Merge "Remove vts10 suite setting" am: 9df87e51af am: a52e54eba7 am: ababcc4d46 am: 766cf3690b

Original change: https://android-review.googlesource.com/c/platform/cts/+/1451856

Change-Id: I20ae86e2d4f01366fad8e39e870476035eec653e
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..03af56d
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,13 @@
+BasedOnStyle: Google
+
+AccessModifierOffset: -4
+AlignOperands: false
+AllowShortFunctionsOnASingleLine: Inline
+AlwaysBreakBeforeMultilineStrings: false
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ConstructorInitializerIndentWidth: 6
+ContinuationIndentWidth: 8
+IndentWidth: 4
+PenaltyBreakBeforeFirstCallParameter: 100000
+SpacesBeforeTrailingComments: 1
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 7b9c427..ff509e0 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -6,6 +6,7 @@
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
                hostsidetests
                tests/tests/binder_ndk
+               tests/tests/view/jni
 
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
@@ -36,6 +37,9 @@
                       tests/tests/view/
                       tests/tests/widget/
                       common/device-side/util/
+                      hostsidetests/car/
+                      hostsidetests/multiuser/
+                      hostsidetests/scopedstorage/
                       hostsidetests/stagedinstall/
                       hostsidetests/userspacereboot/
                       tests/tests/packageinstaller/atomicinstall/
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 3bd63ff..8388c79 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -111,6 +111,7 @@
 
         <activity android:name=".admin.PolicySerializationTestActivity"
                 android:label="@string/da_policy_serialization_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -125,6 +126,7 @@
 
         <activity android:name=".admin.DeviceAdminUninstallTestActivity"
                   android:label="@string/da_uninstall_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -137,6 +139,7 @@
 
         <activity android:name=".admin.tapjacking.DeviceAdminTapjackingTestActivity"
                   android:label="@string/da_tapjacking_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -148,6 +151,7 @@
         </activity>
 
         <receiver android:name=".admin.tapjacking.EmptyDeviceAdminReceiver"
+                  android:exported="true"
                   android:permission="android.permission.BIND_DEVICE_ADMIN">
             <meta-data android:name="android.app.device_admin"
                        android:resource="@xml/tapjacking_device_admin" />
@@ -164,6 +168,7 @@
         <activity
             android:name=".battery.BatterySaverTestActivity"
             android:label="@string/battery_saver_test"
+            android:exported="true"
             android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -175,6 +180,7 @@
 
         <activity android:name=".forcestop.RecentTaskRemovalTestActivity"
                   android:label="@string/remove_from_recents_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -186,6 +192,7 @@
 
         <activity android:name=".companion.CompanionDeviceTestActivity"
                   android:label="@string/companion_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -206,6 +213,7 @@
 
         <activity android:name=".admin.ScreenLockTestActivity"
                 android:label="@string/da_screen_lock_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -221,6 +229,7 @@
         <activity
             android:name=".bluetooth.BluetoothTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bluetooth_test" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -244,6 +253,7 @@
         <activity
             android:name=".bluetooth.BluetoothToggleActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_toggle_bluetooth" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -270,6 +280,7 @@
         <activity
             android:name=".bluetooth.HidDeviceActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_hid_device" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -297,6 +308,7 @@
         <activity
             android:name=".bluetooth.HidHostActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_hid_host" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -320,6 +332,7 @@
         <activity
             android:name=".bluetooth.SecureServerActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_secure_server" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -345,6 +358,7 @@
         <activity
             android:name=".bluetooth.InsecureServerActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_insecure_server" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -370,6 +384,7 @@
         <activity
             android:name=".bluetooth.SecureClientActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_secure_client" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -395,6 +410,7 @@
         <activity
             android:name=".bluetooth.InsecureClientActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_insecure_client" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -421,6 +437,7 @@
         <activity
             android:name=".bluetooth.ConnectionAccessServerActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_connection_access_server" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -443,6 +460,7 @@
         <activity
             android:name=".bluetooth.ConnectionAccessClientActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_connection_access_client" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -504,6 +522,7 @@
         <activity
             android:name=".bluetooth.BleInsecureClientTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_insecure_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -531,6 +550,7 @@
         <activity
             android:name=".bluetooth.BleInsecureClientStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -558,6 +578,7 @@
             android:name=".bluetooth.BleInsecureConnectionPriorityClientTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_connection_priority_client_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -589,6 +610,7 @@
             android:name=".bluetooth.BleInsecureEncryptedClientTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_encrypted_client_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -621,6 +643,7 @@
         <activity
             android:name=".bluetooth.BleInsecureServerTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_insecure_server_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -647,6 +670,7 @@
         <activity
             android:name=".bluetooth.BleInsecureServerStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_server_start_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -673,6 +697,7 @@
         <activity
             android:name=".bluetooth.BleInsecureConnectionPriorityServerTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_connection_priority_server_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -704,6 +729,7 @@
             android:name=".bluetooth.BleInsecureEncryptedServerTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_encrypted_server_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -736,6 +762,7 @@
         <activity
             android:name=".bluetooth.BleSecureClientTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_secure_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -762,6 +789,7 @@
         <activity
             android:name=".bluetooth.BleSecureClientStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -789,6 +817,7 @@
             android:name=".bluetooth.BleSecureConnectionPriorityClientTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_connection_priority_client_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -819,6 +848,7 @@
             android:name=".bluetooth.BleSecureEncryptedClientTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_encrypted_client_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -850,6 +880,7 @@
         <activity
             android:name=".bluetooth.BleSecureServerTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_secure_server_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -876,6 +907,7 @@
         <activity
             android:name=".bluetooth.BleSecureServerStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_server_start_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -902,6 +934,7 @@
         <activity
             android:name=".bluetooth.BleSecureConnectionPriorityServerTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_connection_priority_server_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -932,6 +965,7 @@
             android:name=".bluetooth.BleSecureEncryptedServerTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_encrypted_server_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -963,6 +997,7 @@
         <activity
             android:name=".bluetooth.BleCocInsecureClientTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_insecure_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -989,6 +1024,7 @@
         <activity
             android:name=".bluetooth.BleCocInsecureClientStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1020,6 +1056,7 @@
         <activity
             android:name=".bluetooth.BleCocInsecureServerTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_insecure_server_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1046,6 +1083,7 @@
         <activity
             android:name=".bluetooth.BleCocInsecureServerStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_server_start_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1077,6 +1115,7 @@
         <activity
             android:name=".bluetooth.BleCocSecureClientTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_secure_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1103,6 +1142,7 @@
         <activity
             android:name=".bluetooth.BleCocSecureClientStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1134,6 +1174,7 @@
         <activity
             android:name=".bluetooth.BleCocSecureServerTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_secure_server_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1160,6 +1201,7 @@
         <activity
             android:name=".bluetooth.BleCocSecureServerStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_server_start_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1191,6 +1233,7 @@
         <activity
             android:name=".bluetooth.BleScannerTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_scanner_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1217,6 +1260,7 @@
         <activity
             android:name=".bluetooth.BleScannerPowerLevelActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_power_level_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1246,6 +1290,7 @@
         <activity
             android:name=".bluetooth.BleAdvertiserTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_advertiser_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1272,6 +1317,7 @@
         <activity
             android:name=".bluetooth.BleAdvertiserPowerLevelActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_power_level_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1289,6 +1335,7 @@
 
         <activity android:name=".biometrics.BiometricTestList"
             android:label="@string/biometric_test"
+            android:exported="true"
             android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1304,6 +1351,7 @@
         <activity
             android:name=".biometrics.SensorConfigurationTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_sensor_configuration_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1322,6 +1370,7 @@
         <activity
             android:name=".biometrics.CredentialNotEnrolledTests"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_credential_not_enrolled_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1340,6 +1389,7 @@
         <activity
             android:name=".biometrics.CredentialEnrolledTests"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_credential_enrolled_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1358,6 +1408,7 @@
         <activity
             android:name=".biometrics.CredentialCryptoTests"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_credential_crypto_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1376,6 +1427,7 @@
         <activity
             android:name=".biometrics.BiometricStrongTests"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_strong_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1394,6 +1446,7 @@
         <activity
             android:name=".biometrics.BiometricWeakTests"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_weak_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1412,6 +1465,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationCredentialCipherTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_credential_cipher_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1430,6 +1484,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricCipherTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_cipher_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1448,6 +1503,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricOrCredentialCipherTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_credential_cipher_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1466,6 +1522,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationCredentialSignatureTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_credential_signature_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1484,6 +1541,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricSignatureTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_signature_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1502,6 +1560,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricOrCredentialSignatureTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_or_credential_signature_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1520,6 +1579,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationCredentialMacTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_credential_mac_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1538,6 +1598,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricMacTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_mac_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1556,6 +1617,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricOrCredentialMacTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_or_credential_mac_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1573,6 +1635,7 @@
 
         <activity android:name=".security.IdentityCredentialAuthentication"
                 android:label="@string/sec_identity_credential_authentication_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1587,6 +1650,7 @@
 
         <activity android:name=".security.FingerprintBoundKeysTest"
                 android:label="@string/sec_fingerprint_bound_key_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1601,6 +1665,7 @@
 
         <activity android:name=".security.ProtectedConfirmationTest"
             android:label="@string/sec_protected_confirmation_test"
+            android:exported="true"
             android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1611,6 +1676,7 @@
 
         <activity android:name=".security.ScreenLockBoundKeysTest"
                 android:label="@string/sec_lock_bound_key_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1625,6 +1691,7 @@
 
         <activity android:name=".security.LockConfirmBypassTest"
                 android:label="@string/lock_confirm_test_title"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1639,6 +1706,7 @@
 
         <activity android:name=".security.SetNewPasswordComplexityTest"
                   android:label="@string/set_complexity_test_title"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1653,6 +1721,7 @@
 
         <activity android:name=".streamquality.StreamingVideoActivity"
                 android:label="@string/streaming_video"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1669,7 +1738,8 @@
                 android:screenOrientation="nosensor" />
 
         <!-- FeatureSummaryActivity is replaced by CTS SystemFeaturesTest
-        <activity android:name=".features.FeatureSummaryActivity" android:label="@string/feature_summary">
+        <activity android:name=".features.FeatureSummaryActivity" android:label="@string/feature_summary"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
@@ -1680,6 +1750,7 @@
 
         <activity android:name=".location.LocationListenerActivity"
                 android:label="@string/location_listener_activity"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.location.SET_LOCATION_AND_CHECK" />
@@ -1688,6 +1759,7 @@
         </activity>
 
         <activity android:name=".net.ConnectivityBackgroundTestActivity"
+                android:exported="true"
                 android:label="@string/network_background_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1698,6 +1770,7 @@
         </activity>
 
         <activity android:name=".net.MultiNetworkConnectivityTestActivity"
+                  android:exported="true"
                   android:label="@string/multinetwork_connectivity_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1712,6 +1785,7 @@
 
         <activity android:name=".nfc.NfcTestActivity"
                 android:label="@string/nfc_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2029,7 +2103,8 @@
             <meta-data android:name="android.nfc.cardemulation.off_host_apdu_service" android:resource="@xml/uicc_transaction_event_aid_list"/>
         </service>
 
-        <receiver android:name=".nfc.offhost.UiccTransactionEventReceiver">
+        <receiver android:name=".nfc.offhost.UiccTransactionEventReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.nfc.action.TRANSACTION_DETECTED" >
                 </action>
@@ -2047,6 +2122,7 @@
 
         <!-- Service used for Camera ITS tests -->
         <service android:name=".camera.its.ItsService"
+            android:exported="true"
             android:foregroundServiceType="camera">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.camera.its.START"/>
@@ -2060,6 +2136,7 @@
         -->
         <receiver android:name=".sensors.helpers.SensorDeviceAdminReceiver"
                 android:label="@string/snsr_device_admin_receiver"
+                android:exported="true"
                 android:permission="android.permission.BIND_DEVICE_ADMIN">
             <meta-data android:name="android.app.device_admin"
                        android:resource="@xml/sensor_device_admin" />
@@ -2070,6 +2147,7 @@
 
         <activity android:name=".sensors.AccelerometerMeasurementTestActivity"
                   android:label="@string/snsr_accel_m_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -2084,6 +2162,7 @@
 
         <activity android:name=".sensors.GyroscopeMeasurementTestActivity"
                   android:label="@string/snsr_gyro_m_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -2098,6 +2177,7 @@
 
         <activity android:name=".sensors.HeartRateMonitorTestActivity"
                   android:label="@string/snsr_heartrate_test"
+                  android:exported="true"
                   android:screenOrientation="nosensor">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2110,6 +2190,7 @@
 
         <activity android:name=".sensors.MagneticFieldMeasurementTestActivity"
                   android:label="@string/snsr_mag_m_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2121,6 +2202,7 @@
         </activity>
 
         <activity android:name=".sensors.OffBodySensorTestActivity"
+            android:exported="true"
             android:label="@string/snsr_offbody_sensor_test">
 <!--            <receiver android:name="com.android.cts.verifier.sensors.OffBodySensorTestActivity$AlarmReceiver"></receiver>-->
             <intent-filter>
@@ -2135,6 +2217,7 @@
             android:name=".sensors.RVCVXCheckTestActivity"
             android:keepScreenOn="true"
             android:label="@string/snsr_rvcvxchk_test"
+            android:exported="true"
             android:screenOrientation="locked" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2159,6 +2242,7 @@
         <!-- TODO: enable when a full set of verifications can be implemented -->
         <!--activity android:name=".sensors.RotationVectorTestActivity"
                   android:label="@string/snsr_rot_vec_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2171,6 +2255,7 @@
 
         <activity android:name=".sensors.BatchingTestActivity"
                   android:label="@string/snsr_batch_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2185,6 +2270,7 @@
         <!-- TODO: enable when a more reliable way to identify time synchronization is available -->
         <!--activity android:name=".sensors.SensorSynchronizationTestActivity"
                   android:label="@string/snsr_synch_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2197,6 +2283,7 @@
 
         <activity android:name=".sensors.DynamicSensorDiscoveryTestActivity"
                   android:label="@string/snsr_dynamic_sensor_discovery_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -2211,6 +2298,7 @@
 
         <activity android:name=".camera.formats.CameraFormatsActivity"
                  android:label="@string/camera_format"
+                 android:exported="true"
                  android:screenOrientation="landscape">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2224,6 +2312,7 @@
         </activity>
 
         <activity android:name=".camera.intents.CameraIntentsActivity"
+                 android:exported="true"
                  android:label="@string/camera_intents">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2241,6 +2330,7 @@
 
         <activity android:name=".camera.orientation.CameraOrientationActivity"
                  android:label="@string/camera_orientation"
+                 android:exported="true"
                  android:screenOrientation="landscape">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2257,6 +2347,7 @@
             android:name=".camera.fov.PhotoCaptureActivity"
             android:label="@string/camera_fov_calibration"
             android:screenOrientation="landscape"
+            android:exported="true"
             android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" >
             <intent-filter android:label="@string/camera_fov_calibration" >
                 <action android:name="android.intent.action.MAIN" />
@@ -2285,6 +2376,7 @@
 
         <activity android:name=".camera.video.CameraVideoActivity"
                  android:label="@string/camera_video"
+                 android:exported="true"
                  android:screenOrientation="landscape">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2301,6 +2393,7 @@
                   android:label="@string/camera_its_test"
                   android:launchMode="singleTop"
                   android:configChanges="keyboardHidden|screenSize"
+                  android:exported="true"
                   android:screenOrientation="landscape">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2315,6 +2408,7 @@
 
         <activity android:name=".camera.flashlight.CameraFlashlightActivity"
                   android:label="@string/camera_flashlight_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2328,6 +2422,7 @@
 
         <activity android:name=".camera.performance.CameraPerformanceActivity"
                   android:label="@string/camera_performance_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2342,6 +2437,7 @@
         <activity android:name=".camera.bokeh.CameraBokehActivity"
                   android:label="@string/camera_bokeh_test"
                   android:configChanges="keyboardHidden|screenSize"
+                  android:exported="true"
                   android:screenOrientation="landscape">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2355,6 +2451,7 @@
 
         <activity android:name=".usb.accessory.UsbAccessoryTestActivity"
                 android:label="@string/usb_accessory_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2366,7 +2463,8 @@
                     android:value="android.hardware.type.watch" />
         </activity>
 
-        <activity android:name=".usb.accessory.AccessoryAttachmentHandler">
+        <activity android:name=".usb.accessory.AccessoryAttachmentHandler"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
             </intent-filter>
@@ -2378,6 +2476,7 @@
 <!-- Temporary disabled b/c of incorrect assumptions in part of the test: b/160938927
         <activity android:name=".usb.device.UsbDeviceTestActivity"
                 android:label="@string/usb_device_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2391,7 +2490,8 @@
         </activity>
         -->
 
-        <activity android:name=".usb.mtp.MtpHostTestActivity" android:label="@string/mtp_host_test">
+        <activity android:name=".usb.mtp.MtpHostTestActivity" android:label="@string/mtp_host_test"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
@@ -2405,6 +2505,7 @@
 <!-- Turned off Sensor Power Test in initial L release
         <activity android:name=".sensors.SensorPowerTestActivity"
                 android:label="@string/sensor_power_test"
+                  android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2417,6 +2518,7 @@
 -->
         <activity android:name=".p2p.P2pTestListActivity"
                 android:label="@string/p2p_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2431,6 +2533,7 @@
         </activity>
         <activity android:name=".managedprovisioning.IntermediateRecentActivity"
                   android:label="@string/provisioning_byod_recents"
+                  android:exported="true"
                   android:theme="@android:style/Theme.NoDisplay">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.RECENTS" />
@@ -2439,6 +2542,7 @@
         </activity>
         <activity android:name=".wifi.TestListActivity"
                   android:label="@string/wifi_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2449,6 +2553,7 @@
         </activity>
         <activity android:name=".wifiaware.TestListActivity"
                   android:label="@string/aware_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2459,6 +2564,7 @@
         </activity>
 
         <activity android:name=".notifications.NotificationListenerVerifierActivity"
+                  android:exported="true"
                 android:label="@string/nls_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2467,7 +2573,8 @@
             <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
         </activity>
 
-        <receiver android:name=".notifications.BlockChangeReceiver">
+        <receiver android:name=".notifications.BlockChangeReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"/>
                 <action android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"/>
@@ -2475,13 +2582,22 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name=".notifications.AutomaticZenRuleStatusReceiver">
+        <receiver android:name=".notifications.ActionTriggeredReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.verifier.notifications.ActionTriggeredReceiver"/>
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name=".notifications.AutomaticZenRuleStatusReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.AUTOMATIC_ZEN_RULE_STATUS_CHANGED"/>
             </intent-filter>
         </receiver>
 
         <activity android:name=".notifications.ConditionProviderVerifierActivity"
+                  android:exported="true"
                   android:label="@string/cp_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2501,6 +2617,7 @@
         </activity>
 
         <activity android:name=".notifications.AttentionManagementVerifierActivity"
+                  android:exported="true"
                 android:label="@string/attention_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2512,6 +2629,7 @@
         </activity>
 
         <activity android:name=".notifications.BubblesVerifierActivity"
+                  android:exported="true"
                   android:label="@string/bubbles_notification_title">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2524,6 +2642,7 @@
 
         <activity android:name=".notifications.BubbleActivity"
                   android:label="@string/bubble_activity_title"
+                  android:exported="true"
                   android:resizeableActivity="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND" />
@@ -2537,6 +2656,7 @@
         </activity>
 
         <activity android:name=".notifications.MediaPlayerVerifierActivity"
+                  android:exported="true"
                   android:label="@string/qs_media_player_title">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2569,6 +2689,7 @@
 
         <activity android:name=".notifications.ShortcutThrottlingResetActivity"
             android:label="@string/shortcut_reset_test"
+                  android:exported="true"
             android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2580,6 +2701,7 @@
         </activity>
 
         <activity android:name=".qstiles.TileServiceVerifierActivity"
+                  android:exported="true"
                   android:label="@string/tiles_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2595,6 +2717,7 @@
                  android:icon="@android:drawable/ic_dialog_alert"
                  android:label="@string/tile_service_name"
                  android:enabled="false"
+                  android:exported="true"
                  android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
             <intent-filter>
                 <action android:name="android.service.quicksettings.action.QS_TILE" />
@@ -2603,6 +2726,7 @@
 
         <activity android:name=".vr.VrListenerVerifierActivity"
             android:configChanges="uiMode"
+            android:exported="true"
             android:label="@string/vr_tests">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2657,6 +2781,7 @@
         <service  android:name=".notifications.InteractiveVerifierActivity$DismissService"/>
 
         <activity android:name=".security.CAInstallNotificationVerifierActivity"
+                android:exported="true"
                 android:label="@string/cacert_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2668,6 +2793,7 @@
             <meta-data android:name="test_required_features" android:value="android.software.device_admin" />
         </activity>
         <activity android:name=".security.CANotifyOnBootActivity"
+                android:exported="true"
                 android:label="@string/caboot_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2680,6 +2806,7 @@
         </activity>
 
         <activity android:name=".security.KeyChainTest"
+                android:exported="true"
                 android:label="@string/keychain_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2692,6 +2819,7 @@
         </activity>
 
         <activity android:name=".security.CaCertInstallViaIntentTest"
+                  android:exported="true"
                   android:label="@string/cacert_install_via_intent">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2884,6 +3012,7 @@
                   android:configChanges="keyboardHidden|orientation|screenSize" />
 
         <activity-alias android:name=".CtsVerifierActivity" android:label="@string/app_name"
+                android:exported="true"
                 android:targetActivity=".TestListActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2894,6 +3023,7 @@
 
         <!-- remove comment from the next activity to see the sample test surfacing in the app -->
         <!-- activity android:name=".sample.SampleTestActivity"
+                android:exported="true"
                   android:label="@string/sample_framework_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2903,6 +3033,7 @@
         </activity -->
 
         <activity android:name=".widget.WidgetTestActivity"
+                android:exported="true"
                 android:label="@string/widget_framework_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2916,6 +3047,7 @@
         </activity>
 
         <activity android:name=".deskclock.DeskClockTestsActivity"
+                android:exported="true"
                   android:label="@string/deskclock_tests">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2932,6 +3064,7 @@
         <activity
                 android:name="com.android.cts.verifier.sensors.StepCounterTestActivity"
                 android:label="@string/snsr_step_counter_test"
+                android:exported="true"
                 android:screenOrientation="nosensor" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2946,6 +3079,7 @@
        <activity
             android:name="com.android.cts.verifier.sensors.StepSensorPermissionTestActivity"
             android:label="@string/snsr_step_permission_test"
+                android:exported="true"
             android:screenOrientation="nosensor" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2964,6 +3098,7 @@
         <activity
                 android:name="com.android.cts.verifier.sensors.DeviceSuspendTestActivity"
                 android:label="@string/snsr_device_suspend_test"
+                android:exported="true"
                 android:screenOrientation="nosensor" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2992,6 +3127,7 @@
         <activity
             android:name="com.android.cts.verifier.sensors.SignificantMotionTestActivity"
             android:label="@string/snsr_significant_motion_test"
+                android:exported="true"
             android:screenOrientation="nosensor" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3010,6 +3146,7 @@
         <activity
             android:name="com.android.cts.verifier.sensors.EventSanitizationTestActivity"
             android:label="@string/snsr_event_sanitization_test"
+            android:exported="true"
             android:screenOrientation="nosensor" >
 
             <intent-filter>
@@ -3052,7 +3189,8 @@
             <meta-data android:name="display_mode" android:value="single_display_mode" />
         </activity>
 
-        <receiver android:name=".widget.WidgetCtsProvider">
+        <receiver android:name=".widget.WidgetCtsProvider"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
             </intent-filter>
@@ -3067,6 +3205,7 @@
             android:exported="false" />
 
         <activity android:name=".projection.cube.ProjectionCubeActivity"
+                android:exported="true"
                   android:label="@string/pca_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3077,6 +3216,7 @@
         </activity>
 
         <activity android:name=".projection.widgets.ProjectionWidgetActivity"
+                android:exported="true"
                   android:label="@string/pwa_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3087,6 +3227,7 @@
         </activity>
 
         <activity android:name=".projection.list.ProjectionListActivity"
+                android:exported="true"
                   android:label="@string/pla_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3098,6 +3239,7 @@
         </activity>
 
         <activity android:name=".projection.video.ProjectionVideoActivity"
+                android:exported="true"
                   android:label="@string/pva_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3109,6 +3251,7 @@
         </activity>
 
         <activity android:name=".projection.touch.ProjectionTouchActivity"
+                android:exported="true"
                   android:label="@string/pta_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3121,6 +3264,7 @@
 
 
         <activity android:name=".projection.offscreen.ProjectionOffscreenActivity"
+                android:exported="true"
                   android:label="@string/poa_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3136,6 +3280,7 @@
                  android:process=":projectionservice" />
 
         <activity android:name=".managedprovisioning.DeviceOwnerNegativeTestActivity"
+                android:exported="true"
                 android:label="@string/negative_device_owner">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3150,6 +3295,7 @@
         <activity android:name=".managedprovisioning.DeviceOwnerNegativeTestActivity$TrampolineActivity" />
 
         <activity android:name=".managedprovisioning.EnterprisePrivacyInfoOnlyTestActivity"
+                android:exported="true"
                 android:label="@string/enterprise_privacy_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_ENTERPRISE_PRIVACY_INFO_ONLY" />
@@ -3158,6 +3304,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.DeviceOwnerPositiveTestActivity"
+                android:exported="true"
                 android:label="@string/positive_device_owner">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3172,6 +3319,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.ManagedUserPositiveTestActivity"
+                 android:exported="true"
                   android:label="@string/managed_user_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3181,6 +3329,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.DeviceOwnerRequestingBugreportTestActivity"
+                android:exported="true"
                 android:label="@string/device_owner_requesting_bugreport_tests">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3201,6 +3350,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.CrossProfilePermissionControlActivity"
+                android:exported="true"
                   android:label="@string/provisioning_byod_cross_profile_permission_control">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CROSS_PROFILE_PERMISSION_CONTROL" />
@@ -3213,6 +3363,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.LockTaskUiTestActivity"
+                android:exported="true"
                 android:label="@string/device_owner_lock_task_ui_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.STOP_LOCK_TASK" />
@@ -3225,6 +3376,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.VpnTestActivity"
+                android:exported="true"
                 android:label="@string/device_owner_vpn_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.VPN" />
@@ -3233,6 +3385,7 @@
         </activity>
 
         <service android:name=".managedprovisioning.VpnTestActivity$MyTestVpnService"
+                android:exported="true"
                 android:permission="android.permission.BIND_VPN_SERVICE">
             <intent-filter>
                 <action android:name="android.net.VpnService"/>
@@ -3240,6 +3393,7 @@
         </service>
 
         <activity android:name=".managedprovisioning.AlwaysOnVpnSettingsTestActivity"
+                android:exported="true"
                 android:label="@string/provisioning_byod_always_on_vpn">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.ALWAYS_ON_VPN_SETTINGS_TEST" />
@@ -3248,6 +3402,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.KeyChainTestActivity"
+                android:exported="true"
                 android:label="@string/provisioning_byod_keychain">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.KEYCHAIN" />
@@ -3256,6 +3411,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.PermissionLockdownTestActivity"
+                android:exported="true"
                 android:label="@string/device_profile_owner_permission_lockdown_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_PERMISSION_LOCKDOWN" />
@@ -3265,6 +3421,7 @@
 
         <activity-alias
                 android:name=".managedprovisioning.ManagedProfilePermissionLockdownTestActivity"
+                android:exported="true"
                 android:targetActivity=".managedprovisioning.PermissionLockdownTestActivity">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.MANAGED_PROFILE_CHECK_PERMISSION_LOCKDOWN" />
@@ -3277,6 +3434,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.PolicyTransparencyTestListActivity"
+                android:exported="true"
                 android:label="@string/device_profile_owner_policy_transparency_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_POLICY_TRANSPARENCY" />
@@ -3284,7 +3442,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".managedprovisioning.PolicyTransparencyTestActivity">
+        <activity android:name=".managedprovisioning.PolicyTransparencyTestActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.SHOW_POLICY_TRANSPARENCY_TEST" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -3292,6 +3451,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.EnterprisePrivacyTestListActivity"
+                android:exported="true"
                 android:label="@string/enterprise_privacy_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_ENTERPRISE_PRIVACY" />
@@ -3301,6 +3461,7 @@
 
         <activity android:name=".managedprovisioning.EnterprisePrivacyTestDefaultAppActivity"
                 android:label="@string/enterprise_privacy_default_app"
+                android:exported="true"
                 android:enabled="false">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
@@ -3343,6 +3504,7 @@
 
         <activity android:name=".managedprovisioning.CommandReceiverActivity"
                 android:theme="@android:style/Theme.NoDisplay"
+                android:exported="true"
                 android:noHistory="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.EXECUTE_COMMAND" />
@@ -3350,7 +3512,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".managedprovisioning.SetSupportMessageActivity">
+        <activity android:name=".managedprovisioning.SetSupportMessageActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.SET_SUPPORT_MSG" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -3359,6 +3522,7 @@
 
         <service android:name=".managedprovisioning.PolicyTransparencyTestActivity$DummyInputMethod"
                 android:label="@string/dummy_input_method_label"
+                android:exported="true"
                 android:permission="android.permission.BIND_INPUT_METHOD">
             <intent-filter>
                 <action android:name="android.view.InputMethod" />
@@ -3368,6 +3532,7 @@
 
         <service android:name=".managedprovisioning.PolicyTransparencyTestActivity$DummyAccessibilityService"
                 android:label="@string/dummy_accessibility_service_label"
+                android:exported="true"
                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
             <intent-filter>
                 <action android:name="android.accessibilityservice.AccessibilityService" />
@@ -3375,6 +3540,7 @@
         </service>
 
         <activity android:name=".managedprovisioning.AuthenticationBoundKeyTestActivity"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.AUTH_BOUND_KEY_TEST" />
@@ -3384,6 +3550,7 @@
 
         <activity android:name=".managedprovisioning.ByodFlowTestActivity"
                 android:launchMode="singleTask"
+                android:exported="true"
                 android:label="@string/provisioning_byod">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3406,13 +3573,15 @@
         </activity>
 
         <receiver
-            android:name=".managedprovisioning.ByodFlowTestActivity$ProvisioningCompleteReceiver">
+            android:name=".managedprovisioning.ByodFlowTestActivity$ProvisioningCompleteReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.MANAGED_PROFILE_PROVISIONED" />
             </intent-filter>
         </receiver>
 
         <activity android:name=".managedprovisioning.ByodProvisioningTestActivity"
+                android:exported="true"
                 android:label="@string/provisioning_tests_byod">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3424,7 +3593,8 @@
 
         <activity android:name=".managedprovisioning.ByodProvisioningTestActivity$ProvisioningStartingActivity" />
 
-        <activity android:name=".managedprovisioning.ByodHelperActivity">
+        <activity android:name=".managedprovisioning.ByodHelperActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_QUERY" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_REMOVE" />
@@ -3457,7 +3627,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".managedprovisioning.ByodPrimaryHelperActivity">
+        <activity android:name=".managedprovisioning.ByodPrimaryHelperActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK_IN_PRIMARY" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -3478,7 +3649,8 @@
                 android:resource="@xml/filepaths" />
         </provider>
 
-        <activity android:name=".managedprovisioning.ByodIconSamplerActivity">
+        <activity android:name=".managedprovisioning.ByodIconSamplerActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_SAMPLE_ICON" />
                 <category android:name="android.intent.category.DEFAULT"></category>
@@ -3486,6 +3658,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.HandleIntentActivity"
+                android:exported="true"
                 android:enabled="false">
             <intent-filter>
                 <!-- We need to have at least one activity listening to these intents on the device
@@ -3557,7 +3730,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".managedprovisioning.CrossProfileTestActivity">
+        <activity android:name=".managedprovisioning.CrossProfileTestActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.CROSS_PROFILE_TO_PERSONAL" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.CROSS_PROFILE_TO_WORK" />
@@ -3577,7 +3751,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".managedprovisioning.WorkStatusTestActivity">
+        <activity android:name=".managedprovisioning.WorkStatusTestActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.WORK_STATUS_ICON" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.WORK_STATUS_TOAST" />
@@ -3590,6 +3765,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.WorkProfileWidgetActivity"
+                android:exported="true"
                   android:label="@string/provisioning_byod_work_profile_widget">
         <intent-filter>
                 <action android:name="com.android.cts.verifier.byod.test_work_profile_widget"/>
@@ -3599,6 +3775,7 @@
 
         <receiver android:name=".managedprovisioning.DeviceAdminTestReceiver"
                 android:label="@string/afw_device_admin"
+                android:exported="true"
                 android:permission="android.permission.BIND_DEVICE_ADMIN">
             <meta-data android:name="android.app.device_admin"
                        android:resource="@xml/device_admin_byod" />
@@ -3616,6 +3793,7 @@
         <activity android:name=".os.TimeoutResetActivity"/>
 
         <activity android:name=".tv.TvInputDiscoveryTestActivity"
+                android:exported="true"
                 android:label="@string/tv_input_discover_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3627,6 +3805,7 @@
         </activity>
 
         <activity android:name=".tv.ParentalControlTestActivity"
+                android:exported="true"
                 android:label="@string/tv_parental_control_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3638,6 +3817,7 @@
         </activity>
 
         <activity android:name=".tv.MultipleTracksTestActivity"
+                android:exported="true"
                 android:label="@string/tv_multiple_tracks_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3649,6 +3829,7 @@
         </activity>
 
         <activity android:name=".tv.TimeShiftTestActivity"
+                android:exported="true"
                 android:label="@string/tv_time_shift_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3661,6 +3842,7 @@
 
         <activity android:name=".tv.AppLinkTestActivity"
             android:label="@string/tv_app_link_test"
+                android:exported="true"
             android:launchMode="singleTask">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3672,6 +3854,7 @@
         </activity>
 
         <activity android:name=".tv.MicrophoneDeviceTestActivity"
+                android:exported="true"
                   android:label="@string/tv_microphone_device_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3695,6 +3878,7 @@
         </activity>
         <activity android:name=".tv.display.DisplayHdrCapabilitiesTestActivity"
                   android:label="@string/tv_hdr_capabilities_test"
+                android:exported="true"
                   android:configChanges="orientation|screenSize|density|smallestScreenSize|screenLayout">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3703,12 +3887,23 @@
             <meta-data android:name="test_category" android:value="@string/test_category_tv" />
             <meta-data android:name="test_required_features"
                        android:value="android.software.leanback" />
-            <meta-data android:name="test_required_configs"
-                       android:value="config_tv_panel"/>
+        </activity>
+        <activity android:name=".tv.display.DisplayModesTestActivity"
+                  android:label="@string/tv_display_modes_test"
+                android:exported="true"
+                  android:configChanges="orientation|screenSize|density|smallestScreenSize|screenLayout">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_tv"/>
+            <meta-data android:name="test_required_features"
+                       android:value="android.software.leanback"/>
         </activity>
 
 
         <activity android:name=".screenpinning.ScreenPinningTestActivity"
+                android:exported="true"
             android:label="@string/screen_pinning_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3719,13 +3914,15 @@
                        android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
         </activity>
 
-        <activity android:name=".tv.MockTvInputSetupActivity">
+        <activity android:name=".tv.MockTvInputSetupActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
         </activity>
 
         <activity android:name=".audio.RingerModeActivity"
+                android:exported="true"
                   android:label="@string/ringer_mode_tests">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3738,6 +3935,7 @@
 
         <activity android:name=".audio.HifiUltrasoundTestActivity"
                 android:label="@string/hifi_ultrasound_test"
+                android:exported="true"
                 android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3749,6 +3947,7 @@
 
         <activity android:name=".audio.HifiUltrasoundSpeakerTestActivity"
                 android:label="@string/hifi_ultrasound_speaker_test"
+                android:exported="true"
                 android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3759,6 +3958,7 @@
         </activity>
 
         <activity android:name=".audio.AudioOutputDeviceNotificationsActivity"
+                android:exported="true"
                   android:label="@string/audio_out_devices_notifications_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3770,6 +3970,7 @@
         </activity>
 
         <activity android:name=".audio.AudioInputDeviceNotificationsActivity"
+                android:exported="true"
                   android:label="@string/audio_in_devices_notifications_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3781,6 +3982,7 @@
         </activity>
 
         <activity android:name=".audio.AudioOutputRoutingNotificationsActivity"
+                android:exported="true"
                   android:label="@string/audio_output_routingnotifications_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3792,6 +3994,7 @@
         </activity>
 
         <activity android:name=".audio.AudioInputRoutingNotificationsActivity"
+                android:exported="true"
                   android:label="@string/audio_input_routingnotifications_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3803,6 +4006,7 @@
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralAttributesActivity"
+                android:exported="true"
                   android:label="@string/audio_uap_attribs_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3815,6 +4019,7 @@
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralNotificationsTest"
+                android:exported="true"
                   android:label="@string/audio_uap_notifications_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3827,6 +4032,7 @@
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralPlayActivity"
+                android:exported="true"
                   android:label="@string/audio_uap_play_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3839,6 +4045,7 @@
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralRecordActivity"
+                android:exported="true"
                   android:label="@string/audio_uap_record_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3851,6 +4058,7 @@
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralButtonsActivity"
+                android:exported="true"
             android:label="@string/audio_uap_buttons_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3863,6 +4071,7 @@
         </activity>
 
         <activity android:name=".audio.USBRestrictRecordAActivity"
+                android:exported="true"
                   android:label="@string/audio_usb_restrict_record_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3875,6 +4084,7 @@
         </activity>
 
         <activity android:name=".audio.ProAudioActivity"
+                android:exported="true"
                   android:label="@string/pro_audio_latency_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3884,10 +4094,18 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host:android.hardware.audio.pro" />
         </activity>
 
-        <!-- ProAudio test invokes the "Loopback" App -->
-        <activity android:name="org.drrickorang.loopback"/>
+        <activity android:name=".audio.AnalogHeadsetAudioActivity"
+                android:exported="true"
+            android:label="@string/audio_headset_audio_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+        </activity>
 
         <activity android:name=".audio.AudioLoopbackLatencyActivity"
+                android:exported="true"
                   android:label="@string/audio_loopback_latency_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3900,6 +4118,7 @@
         </activity>
 
         <activity android:name=".audio.MidiActivity"
+                android:exported="true"
                   android:label="@string/midi_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3911,6 +4130,7 @@
         </activity>
 
         <activity android:name=".audio.NDKMidiActivity"
+                android:exported="true"
                   android:label="@string/ndk_midi_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3922,6 +4142,7 @@
         </activity>
 
         <service android:name="com.android.midi.MidiEchoTestService"
+                android:exported="true"
             android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
             <intent-filter>
                 <action android:name="android.media.midi.MidiDeviceService" />
@@ -3931,6 +4152,7 @@
         </service>
 
         <activity android:name=".audio.AudioFrequencyLineActivity"
+                android:exported="true"
                   android:label="@string/audio_frequency_line_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3941,6 +4163,7 @@
         </activity>
 
         <activity android:name=".audio.AudioFrequencySpeakerActivity"
+                android:exported="true"
                   android:label="@string/audio_frequency_speaker_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3951,6 +4174,7 @@
         </activity>
 
         <activity android:name=".audio.AudioFrequencyMicActivity"
+                android:exported="true"
                   android:label="@string/audio_frequency_mic_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3961,6 +4185,7 @@
         </activity>
 
         <activity android:name=".audio.AudioFrequencyUnprocessedActivity"
+                android:exported="true"
                   android:label="@string/audio_frequency_unprocessed_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3971,6 +4196,7 @@
         </activity>
 
         <activity android:name=".audio.AudioFrequencyVoiceRecognitionActivity"
+                android:exported="true"
                   android:label="@string/audio_frequency_voice_recognition_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3981,6 +4207,7 @@
         </activity>
 
         <activity android:name=".audio.AudioAEC"
+                android:exported="true"
                   android:label="@string/audio_aec_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3991,6 +4218,7 @@
         </activity>
 
         <service android:name=".tv.MockTvInputService"
+                android:exported="true"
             android:permission="android.permission.BIND_TV_INPUT">
             <intent-filter>
                 <action android:name="android.media.tv.TvInputService" />
@@ -3999,7 +4227,8 @@
                 android:resource="@xml/mock_tv_input_service" />
         </service>
 
-        <receiver android:name=".tv.TvInputReceiver">
+        <receiver android:name=".tv.TvInputReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
             </intent-filter>
@@ -4008,6 +4237,7 @@
         </receiver>
 
         <activity android:name=".car.CarDockTestActivity"
+                android:exported="true"
                 android:label="@string/car_dock_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4031,6 +4261,7 @@
 
         <!-- See explaination in CarDockTestActivity.java -->
         <activity-alias android:name=".car.CarDockActivity1"
+                android:exported="true"
             android:targetActivity=".car.CarDockActivity" >
             <meta-data
                 android:name="android.dock_home"
@@ -4044,6 +4275,7 @@
 
         <activity-alias android:name=".car.CarDockActivity2"
             android:targetActivity=".car.CarDockActivity"
+                android:exported="true"
             android:enabled="false" >
             <meta-data
                 android:name="android.dock_home"
@@ -4056,6 +4288,7 @@
         </activity-alias>
 
         <activity android:name=".car.GearSelectionTestActivity"
+                android:exported="true"
                 android:label="@string/gear_selection_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4068,6 +4301,7 @@
         </activity>
 
         <activity android:name=".car.NightModeTestActivity"
+                android:exported="true"
                 android:label="@string/night_mode_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4080,6 +4314,7 @@
         </activity>
 
         <activity android:name=".car.ParkingBrakeOnTestActivity"
+                android:exported="true"
                 android:label="@string/parking_brake_on_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4094,6 +4329,7 @@
         <!-- 6DoF sensor test -->
         <activity
                 android:name="com.android.cts.verifier.sensors.sixdof.Activities.StartActivity"
+                android:exported="true"
                 android:label="@string/six_dof_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4107,6 +4343,7 @@
         </activity>
 
         <activity android:name=".voicemail.VoicemailBroadcastActivity"
+                android:exported="true"
           android:label="@string/voicemail_broadcast_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4130,7 +4367,8 @@
                 android:value="config_voice_capable"/>
         </activity>
 
-        <receiver android:name=".voicemail.VoicemailBroadcastReceiver">
+        <receiver android:name=".voicemail.VoicemailBroadcastReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION" />
             </intent-filter>
@@ -4138,6 +4376,7 @@
 
         <activity
             android:name=".voicemail.VisualVoicemailServiceActivity"
+                android:exported="true"
             android:label="@string/visual_voicemail_service_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4157,6 +4396,7 @@
 
         <activity
             android:name=".dialer.DialerIncomingCallTestActivity"
+                android:exported="true"
             android:label="@string/dialer_incoming_call_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4175,6 +4415,7 @@
         </activity>
 
         <service android:name=".dialer.DialerCallTestService"
+                android:exported="true"
             android:permission="android.permission.BIND_INCALL_SERVICE">
             <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
             <intent-filter>
@@ -4184,6 +4425,7 @@
 
         <activity
             android:name=".dialer.DialerShowsHunOnIncomingCallActivity"
+                android:exported="true"
             android:label="@string/dialer_shows_hun_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4203,6 +4445,7 @@
 
         <activity
             android:name=".voicemail.CallSettingsCheckActivity"
+                android:exported="true"
             android:label="@string/call_settings_check_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4222,6 +4465,7 @@
 
         <activity
             android:name=".voicemail.VoicemailSettingsCheckActivity"
+                android:exported="true"
             android:label="@string/ringtone_settings_check_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4241,6 +4485,7 @@
 
         <activity
             android:name=".dialer.DialerImplementsTelecomIntentsActivity"
+                android:exported="true"
             android:label="@string/dialer_telecom_intents_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4269,6 +4514,7 @@
 
         <activity
             android:name=".telecom.EnablePhoneAccountTestActivity"
+                android:exported="true"
             android:label="@string/telecom_enable_phone_account_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4288,6 +4534,7 @@
 
         <activity
             android:name=".telecom.OutgoingCallTestActivity"
+                android:exported="true"
             android:label="@string/telecom_outgoing_call_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4307,6 +4554,7 @@
 
         <activity
             android:name=".telecom.SelfManagedIncomingCallTestActivity"
+                android:exported="true"
             android:label="@string/telecom_incoming_self_mgd_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4326,6 +4574,7 @@
 
         <activity
             android:name=".telecom.IncomingCallTestActivity"
+                android:exported="true"
             android:label="@string/telecom_incoming_call_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4344,6 +4593,7 @@
         </activity>
 
         <activity android:name=".telecom.TelecomDefaultDialerTestActivity"
+                android:exported="true"
                   android:label="@string/telecom_default_dialer_test_title">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4362,6 +4612,7 @@
         </activity>
 
         <activity android:name=".telecom.CtsVerifierInCallUi"
+                android:exported="true"
                   android:label="@string/telecom_in_call_ui_label">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4373,12 +4624,14 @@
             android:label="@string/device_owner_customize_lockscreen_message" />
 
         <service android:name="com.android.cts.verifier.telecom.CtsConnectionService"
+                android:exported="true"
             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
             <intent-filter>
                 <action android:name="android.telecom.ConnectionService" />
             </intent-filter>
         </service>
         <service android:name="com.android.cts.verifier.telecom.CtsSelfManagedConnectionService"
+                android:exported="true"
             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
             <intent-filter>
                 <action android:name="android.telecom.ConnectionService" />
@@ -4386,6 +4639,7 @@
         </service>
 
         <activity android:name=".instantapps.NotificationTestActivity"
+                android:exported="true"
                  android:label="@string/ia_notification">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4395,6 +4649,7 @@
             <meta-data android:name="test_excluded_features" android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.automotive" />
         </activity>
         <activity android:name=".instantapps.RecentAppsTestActivity"
+                android:exported="true"
                  android:label="@string/ia_recents">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4404,6 +4659,7 @@
             <meta-data android:name="test_excluded_features" android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.automotive" />
         </activity>
         <activity android:name=".instantapps.AppInfoTestActivity"
+                android:exported="true"
                  android:label="@string/ia_app_info">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4415,6 +4671,7 @@
         </activity>
 
         <activity android:name=".displaycutout.DisplayCutoutTestActivity"
+                android:exported="true"
                   android:label="@string/display_cutout_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4422,6 +4679,15 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_display_cutout" />
         </activity>
+        <activity android:name=".speech.tts.TtsTestActivity"
+                  android:label="@string/tts_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_other" />
+            <meta-data android:name="test_excluded_features" android:value="android.hardware.type.watch" />
+        </activity>
     </application>
 
     <queries>
diff --git a/apps/CtsVerifier/jni/midi/MidiTestManager.cpp b/apps/CtsVerifier/jni/midi/MidiTestManager.cpp
index beececf..0981a9d 100644
--- a/apps/CtsVerifier/jni/midi/MidiTestManager.cpp
+++ b/apps/CtsVerifier/jni/midi/MidiTestManager.cpp
@@ -16,11 +16,13 @@
 #include <cstring>
 #include <pthread.h>
 #include <unistd.h>
+#include <stdio.h>
 
 #define TAG "MidiTestManager"
 #include <android/log.h>
 #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
 #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
 
 #include "MidiTestManager.h"
 
@@ -131,6 +133,20 @@
     mReceiveStreamPos = 0;
 }
 
+static void logBytes(uint8_t* bytes, int count) {
+    int buffSize = (count * 6) + 1; // count of "0x??, " + '\0';
+
+    char* logBuff = new char[buffSize];
+    for (int dataIndex = 0; dataIndex < count; dataIndex++) {
+        sprintf(logBuff + (dataIndex * 6), "0x%.2X", bytes[dataIndex]);
+        if (dataIndex < count - 1) {
+            sprintf(logBuff + (dataIndex * 6) + 4, ", ");
+        }
+    }
+    ALOGD("%s", logBuff);
+    delete[] logBuff;
+}
+
 /**
  * Compares the supplied bytes against the sent message stream at the current postion
  * and advances the stream position.
@@ -139,15 +155,32 @@
     if (DEBUG) {
         ALOGI("---- matchStream() count:%d", count);
     }
+
+    // a little bit of checking here...
+    if (count < 0) {
+        ALOGE("Negative Byte Count in MidiTestManager::matchStream()");
+        return false;
+    }
+
+    if (count > MESSAGE_MAX_BYTES) {
+        ALOGE("Too Large Byte Count (%d) in MidiTestManager::matchStream()", count);
+        return false;
+    }
+
     bool matches = true;
 
     for (int index = 0; index < count; index++) {
+        // Check for buffer overflow
+        if (mReceiveStreamPos >= mNumTestStreamBytes) {
+            ALOGD("matchStream() out-of-bounds @%d", mReceiveStreamPos);
+            matches = false;
+            break;
+        }
+
         if (bytes[index] != mTestStream[mReceiveStreamPos]) {
             matches = false;
-            if (DEBUG) {
-                ALOGI("---- mismatch @%d [%d : %d]",
-                        index, bytes[index], mTestStream[mReceiveStreamPos]);
-            }
+            ALOGD("---- mismatch @%d [%d : %d]",
+                    index, bytes[index], mTestStream[mReceiveStreamPos]);
         }
         mReceiveStreamPos++;
     }
@@ -155,6 +188,11 @@
     if (DEBUG) {
         ALOGI("  returns:%d", matches);
     }
+
+    if (!matches) {
+        ALOGD("Mismatched Received Data:");
+        logBytes(bytes, count);
+    }
     return matches;
 }
 
diff --git a/apps/CtsVerifier/jni/midi/MidiTestManager.h b/apps/CtsVerifier/jni/midi/MidiTestManager.h
index c594efa..d85420d 100644
--- a/apps/CtsVerifier/jni/midi/MidiTestManager.h
+++ b/apps/CtsVerifier/jni/midi/MidiTestManager.h
@@ -44,6 +44,7 @@
     uint8_t*   mTestStream;
     int     mNumTestStreamBytes;
     int     mReceiveStreamPos;
+    static const int MESSAGE_MAX_BYTES = 1024;
 
     AMidiInputPort* mMidiSendPort;
     AMidiOutputPort* mMidiReceivePort;
@@ -65,6 +66,7 @@
     static const int TESTSTATUS_FAILED_DEVICE = 5;
     static const int TESTSTATUS_FAILED_JNI = 6;
 
+
     bool StartReading(AMidiDevice* nativeReadDevice);
     bool StartWriting(AMidiDevice* nativeWriteDevice);
 };
diff --git a/apps/CtsVerifier/res/layout/audio_dev_notify.xml b/apps/CtsVerifier/res/layout/audio_dev_notify.xml
index aa6d3c4..6fa178d 100644
--- a/apps/CtsVerifier/res/layout/audio_dev_notify.xml
+++ b/apps/CtsVerifier/res/layout/audio_dev_notify.xml
@@ -21,35 +21,11 @@
         style="@style/RootLayoutPadding">
 
         <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <TextView
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:scrollbars="vertical"
-            android:gravity="bottom"
-            android:id="@+id/audio_general_headset_port_exists"
-            android:text="@string/audio_general_headset_port_exists" />
+            android:orientation="vertical">
 
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_no"
-                android:text="@string/audio_general_headset_no" />
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_yes"
-                android:text="@string/audio_general_headset_yes" />
-        </LinearLayout>
+        <include layout="@layout/audio_wired_query_layout" />
 
     <TextView
       android:layout_width="match_parent"
diff --git a/apps/CtsVerifier/res/layout/audio_frequency_line_activity.xml b/apps/CtsVerifier/res/layout/audio_frequency_line_activity.xml
index 41292d1..3ae6d43 100644
--- a/apps/CtsVerifier/res/layout/audio_frequency_line_activity.xml
+++ b/apps/CtsVerifier/res/layout/audio_frequency_line_activity.xml
@@ -30,40 +30,7 @@
             android:layout_height="wrap_content"
             android:orientation="vertical"
         >
-            <TextView
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:scrollbars="vertical"
-                android:gravity="bottom"
-                android:id="@+id/audio_general_headset_port_exists"
-                android:text="@string/audio_general_headset_port_exists" />
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-            >
-
-                <Button
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:id="@+id/audio_general_headset_no"
-                    android:text="@string/audio_general_headset_no"
-                    android:nextFocusForward="@+id/audio_general_headset_yes"
-                    android:nextFocusDown="@+id/audio_frequency_line_plug_ready_btn"
-                    android:nextFocusRight="@+id/audio_general_headset_yes"/>
-
-                <Button
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:id="@+id/audio_general_headset_yes"
-                    android:text="@string/audio_general_headset_yes"
-                    android:nextFocusForward="@+id/audio_frequency_line_plug_ready_btns"
-                    android:nextFocusDown="@+id/audio_frequency_line_plug_ready_btn"
-                    android:nextFocusLeft="@+id/audio_general_headset_no"
-                    android:nextFocusRight="@+id/audio_frequency_line_plug_ready_btn" />
-
-            </LinearLayout>
+            <include layout="@layout/audio_wired_query_layout" />
 
             <TextView
                 android:layout_width="match_parent"
diff --git a/apps/CtsVerifier/res/layout/audio_headset_audio_activity.xml b/apps/CtsVerifier/res/layout/audio_headset_audio_activity.xml
new file mode 100644
index 0000000..005c5e6
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/audio_headset_audio_activity.xml
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <!-- Has Headset Buttons -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/analog_headset_query"/>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:layout_marginLeft="10dp">
+                <Button
+                    android:text="@string/audio_general_yes"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:id="@+id/headset_analog_port_yes"/>
+                <Button
+                    android:text="@string/audio_general_no"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:id="@+id/headset_analog_port_no"/>
+            </LinearLayout>
+        </LinearLayout>
+
+        <!-- Device Connection -->
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/headset_analog_name"/>
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/headset_analog_plug_message"/>
+
+        <!-- Player Controls -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_marginLeft="10dp">
+            <Button
+                android:text="@string/analog_headset_play"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:id="@+id/headset_analog_play"/>
+            <Button
+                android:text="@string/analog_headset_stop"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:id="@+id/headset_analog_stop"/>
+        </LinearLayout>
+    </LinearLayout>
+
+    <!-- Playback Status -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/analog_headset_success_question"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_marginLeft="10dp">
+            <Button
+                android:text="@string/audio_general_yes"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:id="@+id/headset_analog_play_yes"/>
+            <Button
+                android:text="@string/audio_general_no"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:id="@+id/headset_analog_play_no"/>
+        </LinearLayout>
+    </LinearLayout>
+
+    <!-- Keycodes -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/analog_headset_keycodes_label"/>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_marginLeft="10dp">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/analog_headset_headsethook"
+                android:paddingHorizontal="10dp"
+                android:id="@+id/headset_keycode_headsethook"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/analog_headset_volup"
+                android:paddingHorizontal="10dp"
+                android:id="@+id/headset_keycode_volume_up"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/analog_headset_voldown"
+                android:paddingHorizontal="10dp"
+                android:id="@+id/headset_keycode_volume_down"/>
+        </LinearLayout>
+    </LinearLayout>
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml b/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml
index e09475c..16943c9 100644
--- a/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml
+++ b/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml
@@ -25,31 +25,7 @@
         android:layout_height="wrap_content"
         android:orientation="vertical">
 
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:scrollbars="vertical"
-            android:gravity="bottom"
-            android:id="@+id/audio_general_headset_port_exists"
-            android:text="@string/audio_general_headset_port_exists" />
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_no"
-                android:text="@string/audio_general_headset_no" />
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_yes"
-                android:text="@string/audio_general_headset_yes" />
-        </LinearLayout>
+    <include layout="@layout/audio_wired_query_layout" />
 
     <TextView
       android:layout_width="match_parent"
diff --git a/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml b/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml
index dc55e2a..1cdb131 100644
--- a/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml
+++ b/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml
@@ -25,32 +25,7 @@
         android:layout_height="wrap_content"
         android:orientation="vertical">
 
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:scrollbars="vertical"
-            android:gravity="bottom"
-            android:id="@+id/audio_general_headset_port_exists"
-            android:text="@string/audio_general_headset_port_exists" />
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_no"
-                android:text="@string/audio_general_headset_no" />
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_yes"
-                android:text="@string/audio_general_headset_yes" />
-
-        </LinearLayout>
+    <include layout="@layout/audio_wired_query_layout" />
 
     <TextView
       android:layout_width="match_parent"
diff --git a/apps/CtsVerifier/res/layout/audio_wired_query_layout.xml b/apps/CtsVerifier/res/layout/audio_wired_query_layout.xml
new file mode 100644
index 0000000..bc8038b
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/audio_wired_query_layout.xml
@@ -0,0 +1,32 @@
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:scrollbars="vertical"
+        android:gravity="bottom"
+        android:id="@+id/audio_wired_port_exists"
+        android:text="@string/audio_wired_exists" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/audio_wired_no"
+            android:text="@string/audio_wired_no" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/audio_wired_yes"
+            android:text="@string/audio_wired_yes" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/pro_audio.xml b/apps/CtsVerifier/res/layout/pro_audio.xml
index e61ba01..090b080 100644
--- a/apps/CtsVerifier/res/layout/pro_audio.xml
+++ b/apps/CtsVerifier/res/layout/pro_audio.xml
@@ -137,6 +137,16 @@
 
     <include layout="@layout/audio_loopback_footer_layout"/>
 
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:id="@+id/proAudioTestStatusLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
     <include layout="@layout/pass_fail_buttons"/>
 </LinearLayout>
 </ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/tts_main.xml b/apps/CtsVerifier/res/layout/tts_main.xml
new file mode 100644
index 0000000..f4f1cab
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/tts_main.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:padding="16dp">
+
+        <ScrollView android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+
+            <TextView android:id="@+id/status"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/tts_test_steps" />
+
+        </ScrollView>
+
+        <Button android:id="@+id/accessibility_settings_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/tts_accessibility_settings_button" />
+
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 760609b..7fd690a 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2188,6 +2188,15 @@
     <string name="nls_anr">This test checks that notifications are not sent with content that is
         too long. If this test causes the test app to ANR, the test has failed.
     </string>
+    <string name="action_not_sent">SecureActionOnLockScreenTest</string>
+    <string name="action_received">Action Sent - SecureActionOnLockScreenTest</string>
+    <string name="action_test_title">Action</string>
+    <string name="add_screen_lock">Add a secure screen lock of any type</string>
+    <string name="remove_screen_lock">Remove the added screen lock</string>
+    <string name="secure_action_lockscreen">Lock the screen and find the SecureActionOnLockScreenTest
+        notification. Tap on its action. Verify that the keyguard displays on tap, and that the
+        notification text does not update until the passcode is entered. Ensure that the notification
+        text does update once the device is unlocked.</string>
     <string name="msg_extras_preserved">Check that Message extras Bundle was preserved.</string>
     <string name="conversation_section_ordering">If this device supports conversation notifications,
         and groups them into a separate section from alerting and silent non-conversation
@@ -4660,12 +4669,26 @@
     soundbars which are connected. </string>
     <string name="tv_audio_capabilities_receiver_connected">Connect a receiver or
     soundbar which can play Dolby Atmos. </string>
+    <string name="tv_audio_capabilities_atmos_supported">Does your Android TV device support Dolby Atmos? </string>
 
     <!-- HDR Capabilities test -->
     <string name="tv_hdr_capabilities_test">HDR Capabilities Test</string>
+    <string name="tv_hdr_capabilities_test_step_hdr_display">HDR Display</string>
+    <string name="tv_hdr_capabilities_test_step_no_display">No Display</string>
+    <string name="tv_hdr_capabilities_test_step_non_hdr_display">Non HDR Display</string>
     <string name="tv_hdr_capabilities_test_info">This test checks if
         Display.getHdrCapabilities correctly reports the HDR capabilities of the display.
     </string>
+    <string name="tv_hdr_connect_no_hdr_display">Connect a non-HDR display and then
+        press the "%s" button, below.
+    </string>
+    <string name="tv_hdr_connect_hdr_display">Connect an HDR display and press
+        the "%s" button, below.
+    </string>
+    <string name="tv_hdr_disconnect_display">Press the "%1$s" button
+        and disconnect the display within %2$d seconds. Wait at least %3$d seconds and then
+        reconnect the display.
+    </string>
     <string name="tv_panel_hdr_types_reported_are_supported">
         The supported HDR types are: %s\nAre all of them supported by the hardware?
     </string>
@@ -4673,6 +4696,31 @@
         Are there other HDR types which are supported by the hardware, but are not listed above?
     </string>
 
+    <!-- Display Modes Test -->
+    <string name="tv_display_modes_test">Display Modes Test</string>
+    <string name="tv_display_modes_test_info">This test checks if Display.getSupportedModes()
+        and Display.getMode() are correctly reporting the supported screen modes.
+    </string>
+    <string name="tv_display_modes_disconnect_display">
+        Press the "%1$s" button and disconnect the display within %2$d seconds. Wait at least %3$d
+        seconds and then reconnect the display.
+    </string>
+    <string name="tv_display_modes_test_step_no_display">No Display</string>
+    <string name="tv_display_modes_test_step_1080p">1080p Display</string>
+    <string name="tv_display_modes_test_step_2160p">2160p Display</string>
+    <string name="tv_display_modes_start_test_button">Start Test</string>
+    <string name="tv_display_modes_connect_2160p_display">
+        Connect a 2160p display and press the "%s" button, below.
+    </string>
+    <string name="tv_display_modes_connect_1080p_display">
+        Connect a 1080p display and press the "%s" button, below.
+    </string>
+    <string name="tv_panel_display_modes_reported_are_supported">
+        The supported display modes are:\n%s\n\nAre all of the above display modes supported by the hardware?
+    </string>
+    <string name="tv_panel_display_modes_supported_are_reported">
+        Are there other modes which are supported by the hardware, but are not listed above?
+    </string>
     <string name="overlay_view_text">Overlay View Dummy Text</string>
     <string name="custom_rating">Example of input app specific custom rating.</string>
 
@@ -4827,6 +4875,15 @@
     <string name="audio_proaudio_nopa_message">This device does not set the FEATURE_AUDIO_PRO
         flag and therefore does not need to run this test.</string>
 
+    <!-- Various test status strings -->
+    <string name="audio_proaudio_pass">Pass</string>
+    <string name="audio_proaudio_latencytoohigh">Latency is too high</string>
+    <string name="audio_proaudio_confidencetoolow">"Insufficient Confidence value"</string>
+    <string name="audio_proaudio_midinotreported">"No MIDI support reported"</string>
+    <string name="audio_proaudio_usbhostnotreported">"No USB Host Mode support reported"</string>
+    <string name="audio_proaudio_usbperipheralnotreported">"No USB Peripheral Mode support reported"</string>
+    <string name="audio_proaudio_hdminotvalid">HDMI support is reported by not valid.</string>
+
     <!--  MIDI Test -->
     <string name="midi_test">MIDI Test</string>
     <string name="ndk_midi_test">Native MIDI API Test</string>
@@ -4868,9 +4925,9 @@
     <string name="midiFailedJNILbl">Failed - JNI Error.</string>
 
     <!-- Audio general text -->
-    <string name="audio_general_headset_port_exists">Does this device have a headset port?</string>
-    <string name="audio_general_headset_no">No</string>
-    <string name="audio_general_headset_yes">Yes</string>
+    <string name="audio_wired_exists">Does this device support wired USB or Analog audio peripherals?</string>
+    <string name="audio_wired_no">No</string>
+    <string name="audio_wired_yes">Yes</string>
     <string name="audio_general_deficiency_found">WARNING: Some results show potential deficiencies on the system.
     Please consider addressing them for a future release.</string>
     <string name="audio_general_test_passed">Test Result: Successful</string>
@@ -5027,7 +5084,24 @@
     <string name="vr_test_usb_noise_instructions">TEST USB NOISE: Connect USB microphone and position it right next to microphone under test.
         Position speakers 40 cms from device under test. Press [PLAY] to play broadband white noise. Press [TEST]</string>
 
-
+    <!-- Analog Headset Test -->
+    <string name="audio_headset_audio_test">Analog Headset Audio Test</string>
+    <string name="analog_headset_query">Does this Android device have an analog headset jack?</string>
+    <string name="analog_headset_play">Play</string>
+    <string name="analog_headset_stop">Stop</string>
+    <string name="analog_headset_success_question">Was the audio correctly played through the headset/headphones?</string>
+    <string name="analog_headset_keycodes_label">Headset Keycodes</string>
+    <string name="analog_headset_headsethook">HEADSETHOOK</string>
+    <string name="analog_headset_volup">VOLUME_UP</string>
+    <string name="analog_headset_voldown">VOLUME_DOWN</string>
+    <string name="analog_headset_test">Analog Headset Test</string>
+    <string name="analog_headset_test_info">
+        This test tests the following functionality with respect to wired analog headset/headphones.\n
+        1. Correct audio playback.\n
+        2. Plug intents.\n
+        3. Headset keycodes.\n
+        To run this test it is necessary to have an Android device with a 3.5mm analog headset jack and a compatible analog headset with Hook, Volume Up and Volume Down buttons.
+    </string>
     <!-- Audio AEC Test -->
     <string name="audio_aec_test">Audio Acoustic Echo Cancellation (AEC) Test</string>
     <string name="audio_aec_info">
@@ -5313,7 +5387,7 @@
     <string name="proaudio_info">
        This test requires that you have connected a supported USB Audio Peripheral device
        (not a headset) and that peripheral\'s audio outputs are connected to the peripherals\'s
-       audio inputs. Alternatively, for devices with an analog audio jack or USB-c Digital
+       audio inputs. Alternatively, for devices with an analog audio jack or USB-C Digital
        to Analog dongle, a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a>
        can be used. Also, if there is an input level
        control on the peripheral, it must be set to a non-zero value. When the test has
@@ -5614,6 +5688,15 @@
     Note: Devices declaring feature android.hardware.audio.pro MUST implement USB host mode (CDD 5.10 C-1-3) and if they omit a 4 conductor 3.5mm audio jack MUST support USB audio class (CDD 5.10 C-3-1)
     </string>
 
+    <string name="loopback_test_question">Does this device allow for the connection of a loopback audio peripheral?</string>
+    <string name="loopback_dlg_caption">Loopback Peripheral Required</string>
+    <string name="loopback_dlg_text">This test requires an Audio Loopback Peripheral to be connected to the device.\n
+        This can be done in one of three ways:\n
+        1. Connect a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a> to the 3.5mm headset jack.\n
+        2. Connect a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a> to a USB-C headset adapter.\n
+        3. Connect a USB audio interface peripheral and connect the outputs to the inputs with audio patch cables.
+    </string>
+
     <string name="display_cutout_test">DisplayCutout Test</string>
     <string name="display_cutout_test_instruction">\n
     This test is to make sure that the area inside the safe insets from the DisplayCutout should be
@@ -5623,12 +5706,21 @@
     2. All buttons are clickable. \n
     </string>
 
-    <string name="loopback_test_question">Does this device allow for the connection of a loopback audio peripheral?</string>
-    <string name="loopback_dlg_caption">Loopback Peripheral Required</string>
-    <string name="loopback_dlg_text">This test requires an Audio Loopback Peripheral to be connected to the device.\n
-        This can be done in one of three ways:\n
-        1. Connect a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a> to the 3.5mm headset jack.\n
-        2. Connect a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a> to a USB-C headset adapter.\n
-        3. Connect a USB audio interface peripheral and connect the outputs to the inputs with an audio patch cable.
+    <!-- TTS Test Resources -->
+    <string name="tts_test">TTS Test</string>
+    <string name="tts_test_info">
+      1. Install the CtsTtsEngineSelectorTestHelper and CtsTtsEngineSelectorTestHelper2 apps on the device.\n
+      2. Click on the "Go To Accessibility Settings" button.\n
+      3. Go to Text-to-speech output > Preferred engine.\n
+      4. Ensure that two engines are listed, both named "TTS CTS Test Helper App".\n
+      5. Ensure that each engine can be selected.
     </string>
+    <string name="tts_test_steps">
+      1. Install the CtsTtsEngineSelectorTestHelper and CtsTtsEngineSelectorTestHelper2 apps on the device.\n
+      2. Click on the "Go To Accessibility Settings" button.\n
+      3. Go to Text-to-speech output > Preferred engine.\n
+      4. Ensure that two engines are listed, both named "TTS CTS Test Helper App".\n
+      5. Ensure that each engine can be selected.
+    </string>
+    <string name="tts_accessibility_settings_button">Go To Accessibility Settings</string>
 </resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
index f9601de..48f4c6b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -131,8 +131,6 @@
 
     private static final String CONFIG_HDMI_SOURCE = "config_hdmi_source";
 
-    private static final String CONFIG_TV_PANEL = "config_tv_panel";
-
     private static final String CONFIG_QUICK_SETTINGS_SUPPORTED = "config_quick_settings_supported";
 
     /** The config to represent that a test is only needed to run in the main display mode
@@ -430,13 +428,16 @@
                         }
                         break;
                     case CONFIG_HDMI_SOURCE:
-                        if(isTvPanel()) {
-                            return false;
-                        }
-                        break;
-                    case CONFIG_TV_PANEL:
-                        if(!isTvPanel()) {
-                            return false;
+                        final int DEVICE_TYPE_HDMI_SOURCE = 4;
+                        try {
+                            if (!getHdmiDeviceType().contains(DEVICE_TYPE_HDMI_SOURCE)) {
+                                return false;
+                            }
+                        } catch (Exception exception) {
+                            Log.e(
+                                    LOG_TAG,
+                                    "Exception while looking up HDMI device type.",
+                                    exception);
                         }
                         break;
                     case CONFIG_QUICK_SETTINGS_SUPPORTED:
@@ -452,21 +453,6 @@
         return true;
     }
 
-    private boolean isTvPanel() {
-        final int DEVICE_TYPE_HDMI_SOURCE = 4;
-        try {
-            if (getHdmiDeviceType().contains(DEVICE_TYPE_HDMI_SOURCE)) {
-                return false;
-            }
-        } catch (Exception exception) {
-            Log.e(
-                    LOG_TAG,
-                    "Exception while looking up HDMI device type.",
-                    exception);
-        }
-        return true;
-    }
-
     /**
      * Check if the test should be ran by the given display mode.
      *
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
new file mode 100644
index 0000000..3836074
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.audio;
+
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import android.graphics.Color;
+
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.util.Log;
+
+import android.view.KeyEvent;
+import android.view.View;
+
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
+
+// MegaPlayer
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.JavaPlayer;
+import org.hyphonate.megaaudio.player.PlayerBuilder;
+import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
+
+public class AnalogHeadsetAudioActivity
+        extends PassFailButtons.Activity
+        implements View.OnClickListener {
+    private static final String TAG = AnalogHeadsetAudioActivity.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private AudioManager    mAudioManager;
+
+    // UI
+    private Button mHasAnalogPortYesBtn;
+    private Button mHasAnalogPortNoBtn;
+
+    private Button mPlayButton;
+    private Button mStopButton;
+    private Button mPlaybackSuccessBtn;
+    private Button mPlaybackFailBtn;
+
+    private TextView mHeadsetNameText;
+    private TextView mHeadsetPlugMessage;
+
+    private TextView mHeadsetHookText;
+    private TextView mHeadsetVolUpText;
+    private TextView mHeadsetVolDownText;
+
+    // Devices
+    private AudioDeviceInfo mHeadsetDeviceInfo;
+    private boolean mHasHeadsetPort;
+    private boolean mPlugIntentReceived;
+    private boolean mPlaybackSuccess;
+
+    // Intents
+    private HeadsetPlugReceiver mHeadsetPlugReceiver;
+
+    // Buttons
+    private boolean mHasHeadsetHook;
+    private boolean mHasVolUp;
+    private boolean mHasVolDown;
+
+    // Player
+    protected boolean mIsPlaying = false;
+
+    // Mega Player
+    static final int NUM_CHANNELS = 2;
+    static final int SAMPLE_RATE = 48000;
+
+    JavaPlayer mAudioPlayer;
+
+    public AnalogHeadsetAudioActivity() {
+        super();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.audio_headset_audio_activity);
+
+        mHeadsetNameText = (TextView)findViewById(R.id.headset_analog_name);
+        mHeadsetPlugMessage = (TextView)findViewById(R.id.headset_analog_plug_message);
+
+        // Analog Port?
+        mHasAnalogPortYesBtn = (Button)findViewById(R.id.headset_analog_port_yes);
+        mHasAnalogPortYesBtn.setOnClickListener(this);
+        mHasAnalogPortNoBtn = (Button)findViewById(R.id.headset_analog_port_no);
+        mHasAnalogPortNoBtn.setOnClickListener(this);
+
+        // Player Controls.
+        mPlayButton = (Button)findViewById(R.id.headset_analog_play);
+        mPlayButton.setOnClickListener(this);
+        mStopButton = (Button)findViewById(R.id.headset_analog_stop);
+        mStopButton.setOnClickListener(this);
+
+        // Play Status
+        mPlaybackSuccessBtn = (Button)findViewById(R.id.headset_analog_play_yes);
+        mPlaybackSuccessBtn.setOnClickListener(this);
+        mPlaybackFailBtn = (Button)findViewById(R.id.headset_analog_play_no);
+        mPlaybackFailBtn.setOnClickListener(this);
+
+        // Keycodes
+        mHeadsetHookText = (TextView)findViewById(R.id.headset_keycode_headsethook);
+        mHeadsetVolUpText = (TextView)findViewById(R.id.headset_keycode_volume_up);
+        mHeadsetVolDownText = (TextView)findViewById(R.id.headset_keycode_volume_down);
+
+        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
+
+        setupPlayer();
+
+        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+
+        mHeadsetPlugReceiver = new HeadsetPlugReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_HEADSET_PLUG);
+        registerReceiver(mHeadsetPlugReceiver, filter);
+
+        showKeyMessagesState();
+
+        setInfoResources(R.string.analog_headset_test, R.string.analog_headset_test_info, -1);
+
+        getPassButton().setEnabled(false);
+    }
+
+    //
+    // Reporting
+    //
+    private boolean calculatePass() {
+        if (!mHasHeadsetPort) {
+            return true;
+        } else {
+            return mPlugIntentReceived &&
+                    mHeadsetDeviceInfo != null &&
+                    mPlaybackSuccess &&
+                    mHasHeadsetHook && mHasVolUp && mHasVolDown;
+        }
+    }
+
+    private void reportHeadsetPort(boolean has) {
+        mHasHeadsetPort = has;
+        getReportLog().addValue(
+                "User Reports Headset Port",
+                has ? 1 : 0,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+        if (has) {
+            mHasAnalogPortNoBtn.setEnabled(false);
+        } else {
+            mHasAnalogPortYesBtn.setEnabled(false);
+        }
+        enablePlayerButtons(has && mHeadsetDeviceInfo != null);
+
+        if (!has) {
+            // no port, so can't test. Let them pass
+            getPassButton().setEnabled(true);
+        }
+    }
+
+    private void reportPlugIntent(Intent intent) {
+        // [C-1-4] MUST trigger ACTION_HEADSET_PLUG upon a plug insert,
+        // but only after all contacts on plug are touching their relevant segments on the jack.
+        mPlugIntentReceived = true;
+
+        // state - 0 for unplugged, 1 for plugged.
+        // name - Headset type, human readable string
+        // microphone - 1 if headset has a microphone, 0 otherwise
+
+        int state = intent.getIntExtra("state", -1);
+        if (state != -1) {
+
+            StringBuilder sb = new StringBuilder();
+            sb.append("ACTION_HEADSET_PLUG received - " + (state == 0 ? "Unplugged" : "Plugged"));
+
+            String name = intent.getStringExtra("name");
+            if (name != null) {
+                sb.append(" - " + name);
+            }
+
+            int hasMic = intent.getIntExtra("microphone", 0);
+            if (hasMic == 1) {
+                sb.append(" [mic]");
+            }
+
+            mHeadsetPlugMessage.setText(sb.toString());
+        }
+        getReportLog().addValue(
+                "ACTION_HEADSET_PLUG Intent Received. State: ",
+                state,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+    }
+
+    private void reportPlaybackStatus(boolean success) {
+        // [C-1-1] MUST support audio playback to stereo headphones
+        // and stereo headsets with a microphone.
+        mPlaybackSuccess = success;
+        if (success) {
+            mPlaybackFailBtn.setEnabled(false);
+        } else {
+            mPlaybackSuccessBtn.setEnabled(false);
+        }
+        getPassButton().setEnabled(calculatePass());
+
+        getReportLog().addValue(
+                "User reported headset/headphones playback",
+                success ? 1 : 0,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+    }
+
+    //
+    // UI
+    //
+    private void showConnectedDevice() {
+        if (mHeadsetDeviceInfo != null) {
+            mHeadsetNameText.setText(
+                    mHeadsetDeviceInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET
+                    ? "Headset Connected"
+                    : "Headphones Connected");
+        } else {
+            mHeadsetNameText.setText("No Headset/Headphones Connected");
+        }
+    }
+
+    private void enablePlayerButtons(boolean enabled) {
+        mPlayButton.setEnabled(enabled);
+        mStopButton.setEnabled(enabled);
+    }
+
+    private void showKeyMessagesState() {
+        mHeadsetHookText.setTextColor(mHasHeadsetHook ? Color.WHITE : Color.GRAY);
+        mHeadsetVolUpText.setTextColor(mHasVolUp ? Color.WHITE : Color.GRAY);
+        mHeadsetVolDownText.setTextColor(mHasVolDown ? Color.WHITE : Color.GRAY);
+    }
+
+    //
+    // Player
+    //
+    protected void setupPlayer() {
+        //
+        // Allocate the source provider for the sort of signal we want to play
+        //
+        AudioSourceProvider sourceProvider = new SinAudioSourceProvider();
+        try {
+            PlayerBuilder builder = new PlayerBuilder();
+            mAudioPlayer = (JavaPlayer)builder
+                    // choose one or the other of these for a Java or an Oboe player
+                    .setPlayerType(PlayerBuilder.TYPE_JAVA)
+                    // .setPlayerType(PlayerBuilder.PLAYER_OBOE)
+                    .setSourceProvider(sourceProvider)
+                    .build();
+        } catch (PlayerBuilder.BadStateException ex) {
+            Log.e(TAG, "Failed MegaPlayer build.");
+        }
+    }
+
+    protected void startPlay() {
+        if (!mIsPlaying) {
+            mAudioPlayer.setupAudioStream(NUM_CHANNELS, SAMPLE_RATE, 96);
+            mAudioPlayer.startStream();
+            mIsPlaying = true;
+        }
+    }
+
+    protected void stopPlay() {
+        if (mIsPlaying) {
+            mAudioPlayer.stopStream();
+            mAudioPlayer.teardownAudioStream();
+            mIsPlaying = false;
+        }
+    }
+
+    //
+    // View.OnClickHandler
+    //
+    @Override
+    public void onClick(View view) {
+        switch (view.getId()) {
+            case R.id.headset_analog_port_yes:
+                reportHeadsetPort(true);
+                break;
+
+            case R.id.headset_analog_port_no:
+                reportHeadsetPort(false);
+                break;
+
+            case R.id.headset_analog_play:
+                startPlay();
+                break;
+
+            case R.id.headset_analog_stop:
+                stopPlay();
+                break;
+
+            case R.id.headset_analog_play_yes:
+                reportPlaybackStatus(true);
+                break;
+
+            case R.id.headset_analog_play_no:
+                reportPlaybackStatus(false);
+                break;
+        }
+    }
+
+    //
+    // Devices
+    //
+    private void scanPeripheralList(AudioDeviceInfo[] devices) {
+        mHeadsetDeviceInfo = null;
+        for(AudioDeviceInfo devInfo : devices) {
+            if (devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
+                    devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) {
+                mHeadsetDeviceInfo = devInfo;
+
+                getReportLog().addValue(
+                        (devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET
+                                ? "Headset" : "Headphones") + " connected",
+                        0,
+                        ResultType.NEUTRAL,
+                        ResultUnit.NONE);
+                break;
+            }
+        }
+
+        showConnectedDevice();
+        enablePlayerButtons(mHeadsetDeviceInfo != null);
+    }
+
+    private class ConnectListener extends AudioDeviceCallback {
+        /*package*/ ConnectListener() {}
+
+        //
+        // AudioDevicesManager.OnDeviceConnectionListener
+        //
+        @Override
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            Log.i(TAG, "onAudioDevicesAdded() num:" + addedDevices.length);
+
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            Log.i(TAG, "onAudioDevicesRemoved() num:" + removedDevices.length);
+
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+    }
+
+    private class HeadsetPlugReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            reportPlugIntent(intent);
+        }
+    }
+
+    //
+    // Keycodes
+    //
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // Log.i(TAG, "onKeyDown(" + keyCode + ")");
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_HEADSETHOOK:
+                mHasHeadsetHook = true;
+                showKeyMessagesState();
+                getPassButton().setEnabled(calculatePass());
+                break;
+
+            case KeyEvent.KEYCODE_VOLUME_UP:
+                mHasVolUp = true;
+                showKeyMessagesState();
+                getPassButton().setEnabled(calculatePass());
+                break;
+
+            case KeyEvent.KEYCODE_VOLUME_DOWN:
+                mHasVolDown = true;
+                showKeyMessagesState();
+                getPassButton().setEnabled(calculatePass());
+                break;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
index 1ee118d..5af519b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
@@ -51,8 +51,8 @@
 
     OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
 
-    Button mHeadsetPortYes;
-    Button mHeadsetPortNo;
+    Button mWiredPortYes;
+    Button mWiredPortNo;
 
     Button mLoopbackPlugReady;
     Button mTestButton;
@@ -106,19 +106,19 @@
                     Log.i(TAG, "audio loopback test");
                     startAudioTest();
                     break;
-                case R.id.audio_general_headset_yes:
-                    Log.i(TAG, "User confirms Headset Port existence");
+                case R.id.audio_wired_yes:
+                    Log.i(TAG, "User confirms wired Port existence");
                     mLoopbackPlugReady.setEnabled(true);
                     recordHeasetPortFound(true);
-                    mHeadsetPortYes.setEnabled(false);
-                    mHeadsetPortNo.setEnabled(false);
+                    mWiredPortYes.setEnabled(false);
+                    mWiredPortNo.setEnabled(false);
                     break;
-                case R.id.audio_general_headset_no:
-                    Log.i(TAG, "User denies Headset Port existence");
+                case R.id.audio_wired_no:
+                    Log.i(TAG, "User denies wired Port existence");
                     recordHeasetPortFound(false);
                     getPassButton().setEnabled(true);
-                    mHeadsetPortYes.setEnabled(false);
-                    mHeadsetPortNo.setEnabled(false);
+                    mWiredPortYes.setEnabled(false);
+                    mWiredPortNo.setEnabled(false);
                     break;
             }
         }
@@ -129,10 +129,10 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_frequency_line_activity);
 
-        mHeadsetPortYes = (Button)findViewById(R.id.audio_general_headset_yes);
-        mHeadsetPortYes.setOnClickListener(mBtnClickListener);
-        mHeadsetPortNo = (Button)findViewById(R.id.audio_general_headset_no);
-        mHeadsetPortNo.setOnClickListener(mBtnClickListener);
+        mWiredPortYes = (Button)findViewById(R.id.audio_wired_yes);
+        mWiredPortYes.setOnClickListener(mBtnClickListener);
+        mWiredPortNo = (Button)findViewById(R.id.audio_wired_no);
+        mWiredPortNo.setOnClickListener(mBtnClickListener);
 
         mLoopbackPlugReady = (Button)findViewById(R.id.audio_frequency_line_plug_ready_btn);
         mLoopbackPlugReady.setOnClickListener(mBtnClickListener);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
index e253635..64c2314 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
@@ -36,7 +36,7 @@
  * Tests Audio Device Connection events for output by prompting the user to insert/remove a
  * wired headset (or microphone) and noting the presence (or absence) of notifications.
  */
-public class AudioInputDeviceNotificationsActivity extends HeadsetHonorSystemActivity {
+public class AudioInputDeviceNotificationsActivity extends AudioWiredDeviceBaseActivity {
     Context mContext;
 
     TextView mConnectView;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
index eefa9e4..fa82446 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
@@ -22,6 +22,7 @@
 
 import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioRecord;
 
@@ -36,10 +37,15 @@
 import android.widget.Button;
 import android.widget.TextView;
 
-/**
+import org.hyphonate.megaaudio.recorder.RecorderBuilder;
+import org.hyphonate.megaaudio.recorder.Recorder;
+import org.hyphonate.megaaudio.recorder.JavaRecorder;
+import org.hyphonate.megaaudio.recorder.sinks.NopAudioSinkProvider;
+
+/*
  * Tests AudioRecord (re)Routing messages.
  */
-public class AudioInputRoutingNotificationsActivity extends HeadsetHonorSystemActivity {
+public class AudioInputRoutingNotificationsActivity extends AudioWiredDeviceBaseActivity {
     private static final String TAG = "AudioInputRoutingNotificationsActivity";
 
     Button recordBtn;
@@ -51,18 +57,33 @@
 
     OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
 
-    TrivialRecorder mAudioRecorder = new TrivialRecorder();
+    static final int NUM_CHANNELS = 2;
+    static final int SAMPLE_RATE = 48000;
+    int mNumFrames;
+
+    JavaRecorder mAudioRecorder;
 
     private class OnBtnClickListener implements OnClickListener {
         @Override
         public void onClick(View v) {
+            if (mAudioRecorder == null) {
+                return; // failed to create the recorder
+            }
+
             switch (v.getId()) {
                 case R.id.audio_routingnotification_recordBtn:
-                    mAudioRecorder.start();
+                {
+                     mAudioRecorder.startStream();
+
+                    AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
+                    audioRecord.addOnRoutingChangedListener(
+                            new AudioRecordRoutingChangeListener(), new Handler());
+
+                }
                     break;
 
                 case R.id.audio_routingnotification_recordStopBtn:
-                    mAudioRecorder.stop();
+                    mAudioRecorder.stopStream();
                     break;
             }
         }
@@ -102,9 +123,19 @@
 
         mContext = this;
 
-        AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
-        audioRecord.addOnRoutingChangedListener(
-            new AudioRecordRoutingChangeListener(), new Handler());
+        // Setup Recorder
+        mNumFrames = Recorder.calcMinBufferFrames(NUM_CHANNELS, SAMPLE_RATE);
+
+        RecorderBuilder builder = new RecorderBuilder();
+        try {
+            mAudioRecorder = (JavaRecorder) builder
+                    .setRecorderType(RecorderBuilder.TYPE_JAVA)
+                    .setAudioSinkProvider(new NopAudioSinkProvider())
+                    .build();
+            mAudioRecorder.setupAudioStream(NUM_CHANNELS, SAMPLE_RATE, mNumFrames);
+        } catch (RecorderBuilder.BadStateException ex) {
+            Log.e(TAG, "Failed MegaRecorder build.");
+        }
 
         // "Honor System" buttons
         super.setup();
@@ -114,7 +145,9 @@
 
     @Override
     public void onBackPressed () {
-        mAudioRecorder.shutDown();
+        if (mAudioRecorder != null) {
+            mAudioRecorder.stopStream();
+        }
         super.onBackPressed();
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
index ad8ba68..0e4f6da 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
@@ -36,7 +36,7 @@
  * Tests Audio Device Connection events for output devices by prompting the user to
  * insert/remove a wired headset and noting the presence (or absence) of notifications.
  */
-public class AudioOutputDeviceNotificationsActivity extends HeadsetHonorSystemActivity {
+public class AudioOutputDeviceNotificationsActivity extends AudioWiredDeviceBaseActivity {
     Context mContext;
 
     TextView mConnectView;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
index a6d8846..cd66d57 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
@@ -36,12 +36,21 @@
 import android.widget.Button;
 import android.widget.TextView;
 
+import org.hyphonate.megaaudio.player.AudioSource;
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.JavaPlayer;
+import org.hyphonate.megaaudio.player.PlayerBuilder;
+import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
+
 /**
  * Tests AudioTrack and AudioRecord (re)Routing messages.
  */
-public class AudioOutputRoutingNotificationsActivity extends HeadsetHonorSystemActivity {
+public class AudioOutputRoutingNotificationsActivity extends AudioWiredDeviceBaseActivity {
     private static final String TAG = "AudioOutputRoutingNotificationsActivity";
 
+    static final int NUM_CHANNELS = 2;
+    static final int SAMPLE_RATE = 48000;
+
     Context mContext;
 
     Button playBtn;
@@ -51,18 +60,27 @@
 
     int mNumTrackNotifications = 0;
 
-    TrivialPlayer mAudioPlayer = new TrivialPlayer();
+    // Mega Player
+    JavaPlayer mAudioPlayer;
 
     private class OnBtnClickListener implements OnClickListener {
         @Override
         public void onClick(View v) {
+            if (mAudioPlayer == null) {
+                return; // failed to create the player
+            }
             switch (v.getId()) {
                 case R.id.audio_routingnotification_playBtn:
-                    mAudioPlayer.start();
+                {
+                    mAudioPlayer.startStream();
+                    AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
+                    audioTrack.addOnRoutingChangedListener(
+                            new AudioTrackRoutingChangeListener(), new Handler());
+                }
                     break;
 
                 case R.id.audio_routingnotification_playStopBtn:
-                    mAudioPlayer.stop();
+                    mAudioPlayer.stopStream();
                     break;
             }
         }
@@ -102,9 +120,23 @@
         stopBtn = (Button)findViewById(R.id.audio_routingnotification_playStopBtn);
         stopBtn.setOnClickListener(mBtnClickListener);
 
-        AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
-        audioTrack.addOnRoutingChangedListener(
-            new AudioTrackRoutingChangeListener(), new Handler());
+        // Setup Player
+        //
+        // Allocate the source provider for the sort of signal we want to play
+        //
+        AudioSourceProvider sourceProvider = new SinAudioSourceProvider();
+        try {
+            PlayerBuilder builder = new PlayerBuilder();
+            mAudioPlayer = (JavaPlayer)builder
+                    // choose one or the other of these for a Java or an Oboe player
+                    .setPlayerType(PlayerBuilder.TYPE_JAVA)
+                    // .setPlayerType(PlayerBuilder.PLAYER_OBOE)
+                    .setSourceProvider(sourceProvider)
+                    .build();
+            mAudioPlayer.setupAudioStream(NUM_CHANNELS, SAMPLE_RATE, 96);
+        } catch (PlayerBuilder.BadStateException ex) {
+            Log.e(TAG, "Failed MegaPlayer build.");
+        }
 
         // "Honor System" buttons
         super.setup();
@@ -114,7 +146,9 @@
 
     @Override
     public void onBackPressed () {
-        mAudioPlayer.shutDown();
+        if (mAudioPlayer != null) {
+            mAudioPlayer.stopStream();
+        }
         super.onBackPressed();
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
new file mode 100644
index 0000000..2e308f2
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
@@ -0,0 +1,80 @@
+/*
+ * 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.cts.verifier.audio;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import android.content.Context;
+
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.util.Log;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import android.widget.Button;
+
+abstract class AudioWiredDeviceBaseActivity extends PassFailButtons.Activity {
+    private static final String TAG = AudioWiredDeviceBaseActivity.class.getSimpleName();
+
+    private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
+
+    abstract protected void enableTestButtons(boolean enabled);
+
+    private void recordWiredPortFound(boolean found) {
+        getReportLog().addValue(
+                "User Reported Wired Port",
+                found ? 1.0 : 0,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+    }
+
+    protected void setup() {
+        // The "Honor" system buttons
+        ((Button)findViewById(R.id.audio_wired_no)).setOnClickListener(mBtnClickListener);
+        ((Button)findViewById(R.id.audio_wired_yes)).setOnClickListener(mBtnClickListener);
+
+        enableTestButtons(false);
+    }
+
+    private class OnBtnClickListener implements OnClickListener {
+        @Override
+        public void onClick(View v) {
+            switch (v.getId()) {
+                case R.id.audio_wired_no:
+                    Log.i(TAG, "User denies wired device existence");
+                    enableTestButtons(false);
+                    recordWiredPortFound(false);
+                    break;
+
+                case R.id.audio_wired_yes:
+                    Log.i(TAG, "User confirms wired device existence");
+                    enableTestButtons(true);
+                    recordWiredPortFound(true);
+                    break;
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HeadsetHonorSystemActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HeadsetHonorSystemActivity.java
deleted file mode 100644
index a82b994..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HeadsetHonorSystemActivity.java
+++ /dev/null
@@ -1,83 +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.cts.verifier.audio;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import com.android.compatibility.common.util.ReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import android.content.Context;
-
-import android.os.Bundle;
-import android.os.Handler;
-
-import android.util.Log;
-
-import android.view.View;
-import android.view.View.OnClickListener;
-
-import android.widget.Button;
-//import android.widget.TextView;
-
-abstract class HeadsetHonorSystemActivity extends PassFailButtons.Activity {
-    private static final String TAG = "HeadsetHonorSystemActivity";
-
-    private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
-
-    abstract protected void enableTestButtons(boolean enabled);
-
-    private void recordHeadsetPortFound(boolean found) {
-        getReportLog().addValue(
-                "User Reported Headset Port",
-                found ? 1.0 : 0,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-    }
-
-    protected void setup() {
-        // The "Honor" system buttons
-        ((Button)findViewById(R.id.audio_general_headset_no)).
-            setOnClickListener(mBtnClickListener);
-        ((Button)findViewById(R.id.audio_general_headset_yes)).
-            setOnClickListener(mBtnClickListener);
-
-        enableTestButtons(false);
-    }
-
-    private class OnBtnClickListener implements OnClickListener {
-        @Override
-        public void onClick(View v) {
-            switch (v.getId()) {
-                case R.id.audio_general_headset_no:
-                    Log.i(TAG, "User denies Headset Port existence");
-                    enableTestButtons(false);
-                    recordHeadsetPortFound(false);
-                    break;
-
-                case R.id.audio_general_headset_yes:
-                    Log.i(TAG, "User confirms Headset Port existence");
-                    enableTestButtons(true);
-                    recordHeadsetPortFound(true);
-                    break;
-            }
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiActivity.java
index 32cc805..e514cb7c8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiActivity.java
@@ -512,6 +512,20 @@
         return (byte)((cmd << 4) | (channel & 0x0F));
     }
 
+    //
+    // Logging Utility
+    //
+    static void logByteArray(String prefix, byte[] value, int offset, int count) {
+        StringBuilder builder = new StringBuilder(prefix);
+        for (int i = 0; i < count; i++) {
+            builder.append(String.format("0x%02X", value[offset + i]));
+            if (i != value.length - 1) {
+                builder.append(", ");
+            }
+        }
+        Log.d(TAG, builder.toString());
+    }
+
     /**
      * A class to control and represent the state of a given test.
      * It hold the data needed for IO, and the logic for sending, receiving and matching
@@ -537,8 +551,10 @@
         private TestMessage[] mTestMessages;
 
         // - The stream of message data to walk through when MIDI data is received.
+        private int mMIDIDataStreamSize;
         private byte[] mMIDIDataStream;
         private int mReceiveStreamPos;
+        private static final int MESSAGE_MAX_BYTES = 1024;
 
         public MidiTestModule(int deviceType) {
             mIODevice = new MidiIODevice(deviceType);
@@ -694,7 +710,8 @@
                 streamSize += mTestMessages[msgIndex].mMsgBytes.length;
             }
 
-            mMIDIDataStream = new byte[streamSize];
+            mMIDIDataStreamSize = streamSize;
+            mMIDIDataStream = new byte[mMIDIDataStreamSize];
 
             int offset = 0;
             for (int msgIndex = 0; msgIndex < mTestMessages.length; msgIndex++) {
@@ -714,9 +731,31 @@
             if (DEBUG) {
                 Log.i(TAG, "---- matchStream() offset:" + offset + " count:" + count);
             }
+            // a little bit of checking here...
+            if (count < 0) {
+                Log.e(TAG, "Negative Byte Count in MidiActivity::matchStream()");
+                return false;
+            }
+
+            if (count > MESSAGE_MAX_BYTES) {
+                Log.e(TAG, "Too Large Byte Count (" + count + ") in MidiActivity::matchStream()");
+                return false;
+            }
+
             boolean matches = true;
 
             for (int index = 0; index < count; index++) {
+                // Avoid a buffer overrun. Still don't understand why it happens
+                if (mReceiveStreamPos >= mMIDIDataStreamSize) {
+                    // report an error here
+                    Log.d(TAG, "matchStream buffer overrun @" + index +
+                            " of " + mMIDIDataStreamSize);
+                    // Dump the bufer here
+                    logByteArray("Expected: ", mMIDIDataStream, 0, mMIDIDataStreamSize);
+                    matches = false;
+                    break;  // bail
+                }
+
                 if (bytes[offset + index] != mMIDIDataStream[mReceiveStreamPos]) {
                     matches = false;
                     if (DEBUG) {
@@ -730,6 +769,11 @@
             if (DEBUG) {
                 Log.i(TAG, "  returns:" + matches);
             }
+
+            if (!matches) {
+                logByteArray("Received: ", bytes, offset, count);
+            }
+
             return matches;
         }
 
@@ -780,6 +824,9 @@
 
             @Override
             public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
+                if (DEBUG) {
+                    Log.d(TAG, "---- onSend() offset:" + offset + " count:" + count);
+                }
                 if (!matchStream(msg, offset, count)) {
                     mTestMismatched = true;
                 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
index c1714cc..b95e9e3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
@@ -19,6 +19,8 @@
 import android.app.AlertDialog;
 import android.content.DialogInterface;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
+
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 
@@ -62,6 +64,8 @@
 
     Button mRoundTripTestButton;
 
+    TextView mTestStatusLbl;
+
     // Borrowed from PassFailButtons.java
     private static final int INFO_DIALOG_ID = 1337;
     private static final String INFO_DIALOG_TITLE_ID = "infoDialogTitleId";
@@ -159,14 +163,38 @@
         calculatePass();
     }
 
-    private void calculatePass() {
+    private boolean calculatePass() {
         boolean hasPassed = !mClaimsProAudio ||
                 (mClaimsLowLatencyAudio && mClaimsMIDI &&
                 mClaimsUSBHostMode && mClaimsUSBPeripheralMode &&
                 (!mClaimsHDMI || isHDMIValid()) &&
                 mOutputDevInfo != null && mInputDevInfo != null &&
                 mConfidence >= CONFIDENCE_THRESHOLD && mLatencyMillis <= PROAUDIO_LATENCY_MS_LIMIT);
+
         getPassButton().setEnabled(hasPassed);
+        return hasPassed;
+    }
+
+    private void displayTestResults() {
+        boolean hasPassed = calculatePass();
+
+        Resources strings = getResources();
+        if (hasPassed) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_pass));
+        } else if (mClaimsProAudio && mLatencyMillis > PROAUDIO_LATENCY_MS_LIMIT) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_latencytoohigh));
+        } else if (mClaimsProAudio && mConfidence < CONFIDENCE_THRESHOLD) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_confidencetoolow));
+        } else if (!mClaimsMIDI) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_midinotreported));
+        } else if (!mClaimsUSBHostMode) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_usbhostnotreported));
+        } else if (!mClaimsUSBPeripheralMode) {
+            mTestStatusLbl.setText(strings.getString(
+                    R.string.audio_proaudio_usbperipheralnotreported));
+        } else if (mClaimsHDMI && isHDMIValid()) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_hdminotvalid));
+        }
     }
 
     @Override
@@ -209,6 +237,8 @@
         mClaimsHDMICheckBox = (CheckBox)findViewById(R.id.proAudioHasHDMICheckBox);
         mClaimsHDMICheckBox.setOnClickListener(this);
 
+        mTestStatusLbl = (TextView)findViewById(R.id.proAudioTestStatusLbl);
+
         calculatePass();
     }
 
@@ -277,7 +307,7 @@
         switch (view.getId()) {
         case R.id.proAudio_runRoundtripBtn:
             startAudioTest();
-           break;
+            break;
 
         case R.id.proAudioHasHDMICheckBox:
             if (mClaimsHDMICheckBox.isChecked()) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialPlayer.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialPlayer.java
deleted file mode 100644
index af09504..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialPlayer.java
+++ /dev/null
@@ -1,111 +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.cts.verifier.audio;
-
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTrack;
-
-import java.lang.InterruptedException;
-import java.lang.Math;
-import java.lang.Runnable;
-
-public class TrivialPlayer implements Runnable {
-    AudioTrack mAudioTrack;
-    int mBufferSize;
-
-    boolean mPlay;
-    boolean mIsPlaying;
-
-    short[] mAudioData;
-
-    Thread mFillerThread = null;
-
-    public TrivialPlayer() {
-        mBufferSize =
-                AudioTrack.getMinBufferSize(
-                    41000,
-                    AudioFormat.CHANNEL_OUT_STEREO,
-                    AudioFormat.ENCODING_PCM_16BIT);
-        mAudioTrack =
-            new AudioTrack(
-                AudioManager.STREAM_MUSIC,
-                41000,
-                AudioFormat.CHANNEL_OUT_STEREO,
-                AudioFormat.ENCODING_PCM_16BIT,
-                mBufferSize,
-                AudioTrack.MODE_STREAM);
-
-        mPlay = false;
-        mIsPlaying = false;
-
-        // setup audio data (silence will suffice)
-        mAudioData = new short[mBufferSize];
-        for (int index = 0; index < mBufferSize; index++) {
-            // mAudioData[index] = 0;
-            // keep this code since one might want to hear the playnig audio
-            // for debugging/verification.
-            mAudioData[index] =
-                (short)(((Math.random() * 2.0) - 1.0) * (double)Short.MAX_VALUE/2.0);
-        }
-    }
-
-    public AudioTrack getAudioTrack() { return mAudioTrack; }
-
-    public boolean isPlaying() {
-        synchronized (this) {
-            return mIsPlaying;
-        }
-    }
-
-    public void start() {
-        mPlay = true;
-        mFillerThread = new Thread(this);
-        mFillerThread.start();
-    }
-
-    public void stop() {
-        mPlay = false;
-        mFillerThread = null;
-    }
-
-    public void shutDown() {
-        stop();
-        while (isPlaying()) {
-            try {
-                Thread.sleep(10);
-            } catch (InterruptedException ex) {
-            }
-        }
-        mAudioTrack.release();
-    }
-
-    @Override
-    public void run() {
-        mAudioTrack.play();
-        synchronized (this) {
-            mIsPlaying = true;
-        }
-        while (mAudioTrack != null && mPlay) {
-            mAudioTrack.write(mAudioData, 0, mBufferSize);
-        }
-        synchronized (this) {
-            mIsPlaying = false;
-        }
-        mAudioTrack.stop();
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialRecorder.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialRecorder.java
deleted file mode 100644
index f684681..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialRecorder.java
+++ /dev/null
@@ -1,104 +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.cts.verifier.audio;
-
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioRecord;
-
-import android.media.MediaRecorder;
-
-import java.lang.InterruptedException;
-import java.lang.Runnable;
-
-public class TrivialRecorder implements Runnable {
-    AudioRecord mAudioRecord;
-    int mBufferSize;
-
-    boolean mRecord;
-    boolean mIsRecording;
-
-    short[] mAudioData;
-
-    Thread mReaderThread = null;
-
-    public TrivialRecorder() {
-        mBufferSize =
-                AudioRecord.getMinBufferSize(
-                    41000,
-                    AudioFormat.CHANNEL_IN_MONO,
-                    AudioFormat.ENCODING_PCM_16BIT);
-        mAudioRecord =
-            new AudioRecord(
-                MediaRecorder.AudioSource.DEFAULT,
-                41000,
-                AudioFormat.CHANNEL_IN_MONO,
-                AudioFormat.ENCODING_PCM_16BIT,
-                mBufferSize);
-
-        mRecord = false;
-        mIsRecording = false;
-
-        // setup audio data (silence will suffice)
-        mAudioData = new short[mBufferSize];
-    }
-
-    public AudioRecord getAudioRecord() { return mAudioRecord; }
-
-    public boolean mIsRecording() {
-        synchronized (this) {
-            return mIsRecording;
-        }
-    }
-
-    public void start() {
-        mRecord = true;
-        mReaderThread = new Thread(this);
-        mReaderThread.start();
-    }
-
-    public void stop() {
-        mRecord = false;
-        mReaderThread = null;
-    }
-
-    public void shutDown() {
-        stop();
-        while (mIsRecording()) {
-            try {
-                Thread.sleep(10);
-            } catch (InterruptedException ex) {
-            }
-        }
-        mAudioRecord.release();
-    }
-
-    @Override
-    public void run() {
-        mAudioRecord.startRecording();
-        synchronized (this) {
-            mIsRecording = true;
-        }
-       while (mRecord) {
-            mAudioRecord.read(mAudioData, 0, mBufferSize);
-        }
-        mAudioRecord.stop();
-        synchronized (this) {
-            mIsRecording = false;
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
index 8f0a9b0..5a2846c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
@@ -52,9 +52,6 @@
 
     protected final boolean mIsMandatedRequired;
 
-    // This will be overriden...
-    protected  int mSystemSampleRate = 48000;
-
     // Widgets
     private TextView mProfileNameTx;
     private TextView mProfileDescriptionTx;
@@ -138,9 +135,6 @@
 
         mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
         mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
-
-        mSystemSampleRate = Integer.parseInt(
-            mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
     }
 
     protected void connectPeripheralStatusWidgets() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
index fc666aa..d1a7726 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
@@ -17,60 +17,68 @@
 package com.android.cts.verifier.audio;
 
 import android.content.Context;
-import android.media.AudioManager;
 import android.util.Log;
 
-import com.android.cts.verifier.audio.audiolib.SignalGenerator;
-import com.android.cts.verifier.audio.audiolib.StreamPlayer;
-import com.android.cts.verifier.audio.audiolib.WaveTableFloatFiller;
-import com.android.cts.verifier.audio.peripheralprofile.USBDeviceInfoHelper;
+import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
+
+// MegaAudio imports
+import org.hyphonate.megaaudio.player.AudioSource;
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.JavaPlayer;
+import org.hyphonate.megaaudio.player.PlayerBuilder;
+import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
 
 public abstract class USBAudioPeripheralPlayerActivity extends USBAudioPeripheralActivity {
     private static final String TAG = "USBAudioPeripheralPlayerActivity";
 
-    protected  int mSystemBufferSize;
+    // MegaPlayer
+    static final int NUM_CHANNELS = 2;
+    JavaPlayer mAudioPlayer;
 
-    // Player
     protected boolean mIsPlaying = false;
-    protected StreamPlayer mPlayer = null;
-    protected WaveTableFloatFiller mFiller = null;
-
-    protected float[] mWavBuffer = null;
 
     protected boolean mOverridePlayFlag = true;
 
-    private static final int WAVBUFF_SIZE_IN_SAMPLES = 2048;
-
     public USBAudioPeripheralPlayerActivity(boolean requiresMandatePeripheral) {
         super(requiresMandatePeripheral); // Mandated peripheral is NOT required
     }
 
     protected void setupPlayer() {
-        mSystemBufferSize =
-            StreamPlayer.calcNumBurstFrames((AudioManager)getSystemService(Context.AUDIO_SERVICE));
+        AudioSystemParams audioSystemParams = new AudioSystemParams();
+        audioSystemParams.init(this);
 
-        // the +1 is so we can repeat the 0th sample and simplify the interpolation calculation.
-        mWavBuffer = new float[WAVBUFF_SIZE_IN_SAMPLES + 1];
+        int systemSampleRate = audioSystemParams.getSystemSampleRate();
+        int numBufferFrames = audioSystemParams.getSystemBufferFrames();
 
-        SignalGenerator.fillFloatSine(mWavBuffer);
-        mFiller = new WaveTableFloatFiller(mWavBuffer);
-
-        mPlayer = new StreamPlayer();
+        //
+        // Allocate the source provider for the sort of signal we want to play
+        //
+        AudioSourceProvider sourceProvider = new SinAudioSourceProvider();
+        try {
+            PlayerBuilder builder = new PlayerBuilder();
+            mAudioPlayer = (JavaPlayer)builder
+                    // choose one or the other of these for a Java or an Oboe player
+                    .setPlayerType(PlayerBuilder.TYPE_JAVA)
+                    // .setPlayerType(PlayerBuilder.PLAYER_OBOE)
+                    .setSourceProvider(sourceProvider)
+                    .build();
+            mAudioPlayer.setupAudioStream(NUM_CHANNELS, systemSampleRate, numBufferFrames);
+        } catch (PlayerBuilder.BadStateException ex) {
+            Log.e(TAG, "Failed MegaPlayer build.");
+        }
     }
 
     protected void startPlay() {
         if (mOutputDevInfo != null && !mIsPlaying) {
-            int numChans = USBDeviceInfoHelper.calcMaxChannelCount(mOutputDevInfo);
-            mPlayer.open(numChans, mSystemSampleRate, mSystemBufferSize, mFiller);
-            mPlayer.start();
+            mAudioPlayer.startStream();
+
             mIsPlaying = true;
         }
     }
 
     protected void stopPlay() {
         if (mIsPlaying) {
-            mPlayer.stop();
-            mPlayer.close();
+            mAudioPlayer.stopStream();
             mIsPlaying = false;
         }
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
index d51eac3..7c3cb08 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
@@ -18,27 +18,29 @@
 
 import android.graphics.Color;
 import android.os.Bundle;
-import android.os.Looper;
-import android.os.Message;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
 
-import com.android.cts.verifier.audio.audiolib.StreamRecorder;
-import com.android.cts.verifier.audio.audiolib.StreamRecorderListener;
+import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
 import com.android.cts.verifier.audio.audiolib.WaveScopeView;
 
-import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
-import com.android.cts.verifier.audio.peripheralprofile.USBDeviceInfoHelper;
+// MegaAudio imports
+import org.hyphonate.megaaudio.common.BuilderBase;
+import org.hyphonate.megaaudio.recorder.JavaRecorder;
+import org.hyphonate.megaaudio.recorder.RecorderBuilder;
+import org.hyphonate.megaaudio.recorder.sinks.AppCallback;
+import org.hyphonate.megaaudio.recorder.sinks.AppCallbackAudioSinkProvider;
 
 import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
 
 public class USBAudioPeripheralRecordActivity extends USBAudioPeripheralPlayerActivity {
     private static final String TAG = "USBAudioPeripheralRecordActivity";
 
-    // Recorder
-    private StreamRecorder mRecorder = null;
-    private RecordListener mRecordListener = null;
+    // MegaRecorder
+    private static final int NUM_CHANNELS = 2;
+    private JavaRecorder    mRecorder;
+
     private boolean mIsRecording = false;
 
     // Widgets
@@ -53,59 +55,46 @@
         super(false); // Mandated peripheral is NOT required
     }
 
-    private void connectWaveView() {
-        // Log.i(TAG, "connectWaveView() rec:" + (mRecorder != null));
-        if (mRecorder != null) {
-            float[] smplFloatBuff = mRecorder.getBurstBuffer();
-            int numChans = mRecorder.getNumChannels();
-            int numFrames = smplFloatBuff.length / numChans;
-            mWaveView.setPCMFloatBuff(smplFloatBuff, numChans, numFrames);
-            mWaveView.invalidate();
-
-            mRecorder.setListener(mRecordListener);
-        }
-    }
-
     public boolean startRecording(boolean withLoopback) {
         if (mInputDevInfo == null) {
             return false;
         }
 
-        if (mRecorder == null) {
-            mRecorder = new StreamRecorder();
-        } else if (mRecorder.isRecording()) {
-            mRecorder.stop();
-        }
+        AudioSystemParams audioSystemParams = new AudioSystemParams();
+        audioSystemParams.init(this);
 
-        // no reason to do more than 2
-        int numChans = USBDeviceInfoHelper.calcMaxChannelCount(mInputDevInfo);
-        if (numChans > 2) {
-            numChans = 2;
-        }
-        Log.i(TAG, "  numChans:" + numChans);
+        int systemSampleRate = audioSystemParams.getSystemSampleRate();
+        int numBufferFrames = audioSystemParams.getSystemBufferFrames();
 
-        if (mRecorder.open(numChans, mSystemSampleRate, mSystemBufferSize)) {
-            connectWaveView();  // Setup the WaveView
+        try {
+            RecorderBuilder builder = new RecorderBuilder();
+            mRecorder = (JavaRecorder)builder
+                    .setRecorderType(BuilderBase.TYPE_JAVA)
+                    .setAudioSinkProvider(new AppCallbackAudioSinkProvider(new ScopeRefreshCallback()))
+                    .build();
+            mRecorder.setupAudioStream(NUM_CHANNELS, systemSampleRate, numBufferFrames);
 
-            mIsRecording = mRecorder.start();
+            mIsRecording = mRecorder.startStream();
 
             if (withLoopback) {
                 startPlay();
             }
-
-            return mIsRecording;
-        } else {
-            return false;
+        } catch (RecorderBuilder.BadStateException ex) {
+            Log.e(TAG, "Failed MegaRecorder build.");
+            mIsRecording = false;
         }
+
+        return mIsRecording;
     }
 
     public void stopRecording() {
         if (mRecorder != null) {
-            mRecorder.stop();
+            mRecorder.stopStream();
+            mRecorder.teardownAudioStream();
         }
 
-        if (mPlayer != null && mPlayer.isPlaying()) {
-            mPlayer.stop();
+        if (mAudioPlayer != null && mAudioPlayer.isPlaying()) {
+            mAudioPlayer.stopStream();
         }
 
         mIsRecording = false;
@@ -130,9 +119,6 @@
 
         setupPlayer();
 
-        mRecorder = new StreamRecorder();
-        mRecordListener = new RecordListener();
-
         mWaveView = (WaveScopeView)findViewById(R.id.uap_recordWaveView);
         mWaveView.setBackgroundColor(Color.DKGRAY);
         mWaveView.setTraceColor(Color.WHITE);
@@ -195,33 +181,17 @@
         }
     }
 
-    private class RecordListener extends StreamRecorderListener {
-        /*package*/ RecordListener() {
-            super(Looper.getMainLooper());
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            // Log.i(TAG, "RecordListener.HandleMessage(" + msg.what + ")");
-            switch (msg.what) {
-                case MSG_START:
-                    break;
-
-                case MSG_BUFFER_FILL:
-                    mWaveView.invalidate();
-                    break;
-
-                case MSG_STOP:
-                    break;
-            }
-        }
-    }
-
     @Override
     protected void onPause() {
         super.onPause();
 
         stopPlay();
     }
-}
 
+    class ScopeRefreshCallback implements AppCallback {
+        @Override
+        public void onDataReady(float[] audioData, int numFrames) {
+            mWaveView.setPCMFloatBuff(audioData, NUM_CHANNELS, numFrames);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java
deleted file mode 100644
index fd4d6c9..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio.audiolib;
-
-/**
- * An interface for objects which provide streamed audio data to a StreamPlayer instance.
- */
-public interface AudioFiller {
-    /**
-     * Reset a stream to the beginning.
-     */
-    public void reset();
-
-    /**
-     * Process a request for audio data.
-     * @param buffer The buffer to be filled.
-     * @param numFrames The number of frames of audio to provide.
-     * @param numChans The number of channels (in the buffer) required by the player.
-     * @return The number of frames actually generated. If this value is less than that
-     * requested, it may be interpreted by the player as the end of playback.
-     */
-    public int fill(float[] buffer, int numFrames, int numChans);
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioSystemParams.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioSystemParams.java
new file mode 100644
index 0000000..d013589
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioSystemParams.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.audio.audiolib;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+public class AudioSystemParams {
+    // This value will be calculated in init()
+    private int mSystemSampleRate;
+
+    // This value will be calculated in init()
+    private int mSystemBufferFrames;
+
+    // The system burst buffer size as reported by AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER
+    // This value will be calculated in init()
+    private int mSystemBurstFrames;
+
+    public void init(Context context) {
+        AudioManager audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
+
+        String framesPerBuffer = audioManager.getProperty(
+                AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+        mSystemBurstFrames = Integer.parseInt(framesPerBuffer, 10);
+
+        mSystemBufferFrames = mSystemBurstFrames;
+
+        mSystemSampleRate = Integer.parseInt(
+                audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
+    }
+
+    public int getSystemSampleRate() {
+        return mSystemSampleRate;
+    }
+
+    public int getSystemBufferFrames() {
+        return mSystemBufferFrames;
+    }
+
+    public int getSystemBurstFrames()  {
+        return mSystemBurstFrames;
+    }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java
deleted file mode 100644
index 2d91acf..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio.audiolib;
-
-/**
- * Generates buffers of PCM data.
- */
-public class SignalGenerator {
-    @SuppressWarnings("unused")
-    private static final String TAG = "SignalGenerator";
-
-    /**
-     * Fills a PCMFloat buffer with 1 cycle of a sine wave.
-     * NOTE: The first and last (index 0 and size-1) are filled with the
-     * sample value because WaveTableFloatFiller assumes this (to make the
-     * interpolation calculation at the end of wavetable more efficient.
-     */
-    static public void fillFloatSine(float[] buffer) {
-        int size = buffer.length;
-        float incr = ((float)Math.PI  * 2.0f) / (float)(size - 1);
-        for(int index = 0; index < size; index++) {
-            buffer[index] = (float)Math.sin(index * incr);
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java
deleted file mode 100644
index bebc2a7..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio.audiolib;
-
-import android.content.Context;
-import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTrack;
-
-import android.util.Log;
-
-/**
- * Plays audio data from a stream. Audio data comes from a provided AudioFiller subclass instance.
- */
-public class StreamPlayer {
-    @SuppressWarnings("unused")
-    private static String TAG = "StreamPlayer";
-
-    private int mSampleRate;
-    private int mNumChans;
-
-    private AudioFiller mFiller;
-
-    private Thread mPlayerThread;
-
-    private AudioTrack mAudioTrack;
-    private int mNumAudioTrackFrames; // number of frames for INTERNAL AudioTrack buffer
-
-    // The Burst Buffer. This is the buffer we fill with audio and feed into the AudioTrack.
-    private int mNumBurstFrames;
-    private float[] mBurstBuffer;
-
-    private float[] mChanGains;
-    private volatile boolean mPlaying;
-
-    private AudioDeviceInfo mRoutingDevice;
-
-    public StreamPlayer() {}
-
-    public int getSampleRate() { return mSampleRate; }
-    public int getNumBurstFrames() { return mNumBurstFrames; }
-
-    public void setChanGains(float[] chanGains) {
-        mChanGains = chanGains;
-    }
-
-    public boolean isPlaying() { return mPlaying; }
-
-    private void applyChannelGains() {
-        if (mChanGains != null) {
-            int buffIndex = 0;
-            for (int frame = 0; frame < mNumBurstFrames; frame++) {
-                for (int chan = 0; chan < mNumChans; chan++) {
-                    mBurstBuffer[buffIndex++] *= mChanGains[chan];
-                }
-            }
-        }
-    }
-
-    public void setFiller(AudioFiller filler) { mFiller = filler; }
-
-    public void setRouting(AudioDeviceInfo routingDevice) {
-        mRoutingDevice = routingDevice;
-        if (mAudioTrack != null) {
-            mAudioTrack.setPreferredDevice(mRoutingDevice);
-        }
-    }
-
-    public AudioTrack getAudioTrack() { return mAudioTrack; }
-
-    private void allocBurstBuffer() {
-        mBurstBuffer = new float[mNumBurstFrames * mNumChans];
-    }
-
-    private static int calcNumBufferBytes(int sampleRate, int numChannels, int encoding) {
-        return AudioTrack.getMinBufferSize(sampleRate,
-                    AudioUtils.countToOutPositionMask(numChannels),
-                    encoding);
-    }
-
-    private static int calcNumBufferFrames(int sampleRate, int numChannels, int encoding) {
-        return calcNumBufferBytes(sampleRate, numChannels, encoding)
-                / AudioUtils.calcFrameSizeInBytes(encoding, numChannels);
-    }
-
-    public static int calcNumBurstFrames(AudioManager am) {
-        String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
-        return Integer.parseInt(framesPerBuffer, 10);
-    }
-
-    public boolean open(int numChans, int sampleRate, int numBurstFrames, AudioFiller filler) {
-//        Log.i(TAG, "StreamPlayer.open(chans:" + numChans + ", rate:" + sampleRate +
-//                ", frames:" + numBurstFrames);
-
-        mNumChans = numChans;
-        mSampleRate = sampleRate;
-        mNumBurstFrames = numBurstFrames;
-
-        mNumAudioTrackFrames =
-                calcNumBufferFrames(sampleRate, numChans, AudioFormat.ENCODING_PCM_FLOAT);
-
-        mFiller = filler;
-
-        int bufferSizeInBytes = mNumAudioTrackFrames *
-                AudioUtils.calcFrameSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT, mNumChans);
-        try {
-            mAudioTrack = new AudioTrack.Builder()
-                    .setAudioFormat(new AudioFormat.Builder()
-                            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
-                            .setSampleRate(mSampleRate)
-                            .setChannelIndexMask(AudioUtils.countToIndexMask(mNumChans))
-                            .build())
-                    .setBufferSizeInBytes(bufferSizeInBytes)
-                    .build();
-
-            allocBurstBuffer();
-            return true;
-        }  catch (UnsupportedOperationException ex) {
-            Log.e(TAG, "Couldn't open AudioTrack: " + ex);
-            mAudioTrack = null;
-            return false;
-        }
-    }
-
-    private void waitForPlayerThreadToExit() {
-        try {
-            if (mPlayerThread != null) {
-                mPlayerThread.join();
-                mPlayerThread = null;
-            }
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-    }
-
-    public void close() {
-        stop();
-
-        waitForPlayerThreadToExit();
-
-        if (mAudioTrack != null) {
-            mAudioTrack.release();
-            mAudioTrack = null;
-        }
-    }
-
-    public boolean start() {
-        if (!mPlaying && mAudioTrack != null) {
-            mPlaying = true;
-
-            waitForPlayerThreadToExit(); // just to be sure.
-
-            mPlayerThread = new Thread(new StreamPlayerRunnable(), "StreamPlayer Thread");
-            mPlayerThread.start();
-
-            return true;
-        }
-
-        return false;
-    }
-
-    public void stop() {
-        mPlaying = false;
-    }
-
-    //
-    // StreamPlayerRunnable
-    //
-    private class StreamPlayerRunnable implements Runnable {
-        @Override
-        public void run() {
-            final int numBurstSamples = mNumBurstFrames * mNumChans;
-
-            mAudioTrack.play();
-            while (true) {
-                boolean playing;
-                synchronized(this) {
-                    playing = mPlaying;
-                }
-                if (!playing) {
-                    break;
-                }
-
-                mFiller.fill(mBurstBuffer, mNumBurstFrames, mNumChans);
-                if (mChanGains != null) {
-                    applyChannelGains();
-                }
-                int numSamplesWritten =
-                        mAudioTrack.write(mBurstBuffer, 0, numBurstSamples, AudioTrack.WRITE_BLOCKING);
-                if (numSamplesWritten < 0) {
-                    // error
-                    Log.i(TAG, "AudioTrack write error: " + numSamplesWritten);
-                    stop();
-                } else if (numSamplesWritten < numBurstSamples) {
-                    // end of stream
-                    Log.i(TAG, "Stream Complete.");
-                    stop();
-                }
-            }
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java
deleted file mode 100644
index 2ec742e..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio.audiolib;
-
-import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
-import android.media.AudioRecord;
-
-import android.util.Log;
-
-import java.lang.Math;
-
-/**
- * Records audio data to a stream.
- */
-public class StreamRecorder {
-    @SuppressWarnings("unused")
-    private static final String TAG = "StreamRecorder";
-
-    // Sample Buffer
-    private float[] mBurstBuffer;
-    private int mNumBurstFrames;
-    private int mNumChannels;
-
-    // Recording attributes
-    private int mSampleRate;
-
-    // Recording state
-    Thread mRecorderThread = null;
-    private AudioRecord mAudioRecord = null;
-    private boolean mRecording = false;
-
-    private StreamRecorderListener mListener = null;
-
-    private AudioDeviceInfo mRoutingDevice = null;
-
-    public StreamRecorder() {}
-
-    public int getNumBurstFrames() { return mNumBurstFrames; }
-    public int getSampleRate() { return mSampleRate; }
-
-    /*
-     * State
-     */
-    public static int calcNumBufferBytes(int numChannels, int sampleRate, int encoding) {
-        // NOTE: Special handling of 4-channels. There is currently no AudioFormat positional
-        // constant for 4-channels of input, so in this case, calculate for 2 and double it.
-        int numBytes = 0;
-        if (numChannels == 4) {
-            numBytes = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_IN_STEREO,
-                    encoding);
-            numBytes *= 2;
-        } else {
-            numBytes = AudioRecord.getMinBufferSize(sampleRate,
-                    AudioUtils.countToInPositionMask(numChannels), encoding);
-        }
-
-        return numBytes;
-    }
-
-    public static int calcNumBufferFrames(int numChannels, int sampleRate, int encoding) {
-        return calcNumBufferBytes(numChannels, sampleRate, encoding) /
-                AudioUtils.calcFrameSizeInBytes(encoding, numChannels);
-    }
-
-    public boolean isInitialized() {
-        return mAudioRecord != null && mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED;
-    }
-
-    public boolean isRecording() { return mRecording; }
-
-    public void setRouting(AudioDeviceInfo routingDevice) {
-        Log.i(TAG, "setRouting(" + (routingDevice != null ? routingDevice.getId() : -1) + ")");
-        mRoutingDevice = routingDevice;
-        if (mAudioRecord != null) {
-            mAudioRecord.setPreferredDevice(mRoutingDevice);
-        }
-    }
-
-    /*
-     * Accessors
-     */
-    public float[] getBurstBuffer() { return mBurstBuffer; }
-
-    public int getNumChannels() { return mNumChannels; }
-
-    /*
-     * Events
-     */
-    public void setListener(StreamRecorderListener listener) {
-        mListener = listener;
-    }
-
-    private void waitForRecorderThreadToExit() {
-        try {
-            if (mRecorderThread != null) {
-                mRecorderThread.join();
-                mRecorderThread = null;
-            }
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-    }
-
-    private boolean open_internal(int numChans, int sampleRate) {
-        mNumChannels = numChans;
-        mSampleRate = sampleRate;
-
-        final int frameSize =
-                AudioUtils.calcFrameSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT, mNumChannels);
-        final int bufferSizeInBytes = frameSize * 64;   // Some, non-critical value
-
-        AudioFormat.Builder formatBuilder = new AudioFormat.Builder();
-        formatBuilder.setEncoding(AudioFormat.ENCODING_PCM_FLOAT);
-        formatBuilder.setSampleRate(mSampleRate);
-
-        if (numChans <= 2) {
-            // There is currently a bug causing channel INDEX masks to fail.
-            // for channels counts of <= 2, use channel POSITION
-            final int chanPosMask = AudioUtils.countToInPositionMask(numChans);
-            formatBuilder.setChannelMask(chanPosMask);
-        } else {
-            // There are no INPUT channel-position masks for > 2 channels
-            final int chanIndexMask = AudioUtils.countToIndexMask(numChans);
-            formatBuilder.setChannelIndexMask(chanIndexMask);
-        }
-
-        AudioRecord.Builder builder = new AudioRecord.Builder();
-        builder.setAudioFormat(formatBuilder.build());
-
-        try {
-            mAudioRecord = builder.build();
-            return true;
-        } catch (UnsupportedOperationException ex) {
-            Log.e(TAG, "Couldn't open AudioRecord: " + ex);
-            mAudioRecord = null;
-            return false;
-        }
-    }
-
-    public boolean open(int numChans, int sampleRate, int numBurstFrames) {
-        boolean sucess = open_internal(numChans, sampleRate);
-        if (sucess) {
-            mNumBurstFrames = numBurstFrames;
-            mBurstBuffer = new float[mNumBurstFrames * mNumChannels];
-            // put some non-zero data in the burst buffer.
-            // this is to verify that the record is putting SOMETHING into each channel.
-            for(int index = 0; index < mBurstBuffer.length; index++) {
-                mBurstBuffer[index] = (float)(Math.random() * 2.0) - 1.0f;
-            }
-        }
-
-        return sucess;
-    }
-
-    public void close() {
-        stop();
-
-        waitForRecorderThreadToExit();
-
-        mAudioRecord.release();
-        mAudioRecord = null;
-    }
-
-    public boolean start() {
-        mAudioRecord.setPreferredDevice(mRoutingDevice);
-
-        if (mListener != null) {
-            mListener.sendEmptyMessage(StreamRecorderListener.MSG_START);
-        }
-
-        try {
-            mAudioRecord.startRecording();
-        } catch (IllegalStateException ex) {
-            Log.i("", "ex: " + ex);
-        }
-        mRecording = true;
-
-        waitForRecorderThreadToExit(); // just to be sure.
-
-        mRecorderThread = new Thread(new StreamRecorderRunnable(), "StreamRecorder Thread");
-        mRecorderThread.start();
-
-        return true;
-    }
-
-    public void stop() {
-        if (mRecording) {
-            mRecording = false;
-        }
-    }
-
-    /*
-     * StreamRecorderRunnable
-     */
-    private class StreamRecorderRunnable implements Runnable {
-        @Override
-        public void run() {
-            final int numBurstSamples = mNumBurstFrames * mNumChannels;
-            while (mRecording) {
-                int numReadSamples = mAudioRecord.read(
-                        mBurstBuffer, 0, numBurstSamples, AudioRecord.READ_BLOCKING);
-
-                if (numReadSamples < 0) {
-                    // error
-                    Log.i(TAG, "AudioRecord write error: " + numReadSamples);
-                    stop();
-                } else if (numReadSamples < numBurstSamples) {
-                    // got less than requested?
-                    Log.i(TAG, "AudioRecord Underflow: " + numReadSamples +
-                            " vs. " + numBurstSamples);
-                    stop();
-                }
-
-                if (mListener != null && numReadSamples == numBurstSamples) {
-                    mListener.sendEmptyMessage(StreamRecorderListener.MSG_BUFFER_FILL);
-                }
-            }
-
-            if (mListener != null) {
-                // TODO: on error or underrun we may be send bogus data.
-                mListener.sendEmptyMessage(StreamRecorderListener.MSG_STOP);
-            }
-            mAudioRecord.stop();
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java
deleted file mode 100644
index c542432..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio.audiolib;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-
-public class StreamRecorderListener extends Handler {
-    @SuppressWarnings("unused")
-    private static final String TAG = "StreamRecorderListener";
-
-    public static final int MSG_START = 0;
-    public static final int MSG_BUFFER_FILL = 1;
-    public static final int MSG_STOP = 2;
-
-    public StreamRecorderListener(Looper looper) {
-        super(looper);
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case MSG_START:
-            case MSG_BUFFER_FILL:
-            case MSG_STOP:
-                break;
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java
deleted file mode 100644
index 1040eab..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio.audiolib;
-
-/**
- * A AudioFiller implementation for feeding data from a PCMFLOAT wavetable.
- */
-public class WaveTableFloatFiller implements AudioFiller {
-    @SuppressWarnings("unused")
-    private static String TAG = "WaveTableFloatFiller";
-
-    private float[] mWaveTbl = null;
-    private int mNumWaveTblSamples = 0;
-    private float mSrcPhase = 0.0f;
-
-    private float mSampleRate = 48000;
-    private float mFreq = 1000; // some arbitrary frequency
-    private float mFN = 1.0f;   // The "nominal" frequency, essentially how much much of the
-                                // wave table needs to be played to get one cycle at the
-                                // sample rate. Used to calculate the phase increment
-
-    public WaveTableFloatFiller(float[] waveTbl) {
-        setWaveTable(waveTbl);
-    }
-
-    private void calcFN() {
-        mFN = mSampleRate / (float)mNumWaveTblSamples;
-    }
-
-    public void setWaveTable(float[] waveTbl) {
-        mWaveTbl = waveTbl;
-        mNumWaveTblSamples = waveTbl != null ? mWaveTbl.length - 1 : 0;
-
-        calcFN();
-    }
-
-    public void setSampleRate(float sampleRate) {
-        mSampleRate = sampleRate;
-        calcFN();
-    }
-
-    public void setFreq(float freq) {
-        mFreq = freq;
-    }
-
-    @Override
-    public void reset() {
-        mSrcPhase = 0.0f;
-    }
-
-    public int fill(float[] buffer, int numFrames, int numChans) {
-        final float phaseIncr = mFreq / mFN;
-        int outIndex = 0;
-        for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
-            // 'mod' back into the waveTable
-            while (mSrcPhase >= (float)mNumWaveTblSamples) {
-                mSrcPhase -= (float)mNumWaveTblSamples;
-            }
-
-            // linear-interpolate
-            int srcIndex = (int)mSrcPhase;
-            float delta0 = mSrcPhase - (float)srcIndex;
-            float delta1 = 1.0f - delta0;
-            float value = ((mWaveTbl[srcIndex] * delta0) + (mWaveTbl[srcIndex + 1] * delta1));
-
-            for (int chanIndex = 0; chanIndex < numChans; chanIndex++) {
-                buffer[outIndex++] = value;
-            }
-
-            mSrcPhase += phaseIncr;
-        }
-
-        return numFrames;
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/battery/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/battery/OWNERS
new file mode 100644
index 0000000..854a7f4
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/battery/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 330055
+omakoto@google.com
+kwekua@google.com
+yamasani@google.com
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/displaycutout/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/displaycutout/OWNERS
new file mode 100644
index 0000000..545d263
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/displaycutout/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 339570
+roosa@google.com
+shawnlin@google.com
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java
new file mode 100644
index 0000000..409dcd9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.notifications;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.cts.verifier.R;
+
+public class ActionTriggeredReceiver extends BroadcastReceiver {
+
+    public static String ACTION = "com.android.cts.verifier.notifications.ActionTriggeredReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (ACTION.equals(intent.getAction())) {
+            sendNotification(context, false);
+        }
+    }
+
+    public static void sendNotification(Context context, boolean initialSend) {
+        String initialSendText = context.getString(R.string.action_not_sent);
+        String updateSendText = context.getString(R.string.action_received);
+        NotificationManager nm = context.getSystemService(NotificationManager.class);
+        Notification n1 = new Notification.Builder(
+                context, NotificationListenerVerifierActivity.TAG)
+                .setContentTitle(initialSend ?  initialSendText: updateSendText)
+                .setContentText(initialSend ? initialSendText : updateSendText)
+                .setSmallIcon(R.drawable.ic_stat_charlie)
+                .setCategory(Notification.CATEGORY_REMINDER)
+                .addAction(new Notification.Action.Builder(R.drawable.ic_stat_charlie,
+                        context.getString(R.string.action_test_title),
+                        makeBroadcastIntent(context))
+                        .setAuthenticationRequired(true).build())
+                .build();
+        nm.notify(ACTION, 0, n1);
+    }
+
+    protected static PendingIntent makeBroadcastIntent(Context context) {
+        Intent intent = new Intent(ACTION);
+        intent.setComponent(new ComponentName(context, ActionTriggeredReceiver.class));
+        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        return pi;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
index 121534a..0c7077e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -419,6 +419,14 @@
         return pi;
     }
 
+    protected PendingIntent makeBroadcastIntent(int code, String tag) {
+        Intent intent = new Intent(tag);
+        intent.setComponent(new ComponentName(mContext, ActionTriggeredReceiver.class));
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, code, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        return pi;
+    }
+
     protected boolean checkEquals(long[] expected, long[] actual, String message) {
         if (Arrays.equals(expected, actual)) {
             return true;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index 1d704b0..f49be5c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -35,6 +35,7 @@
 import static com.android.cts.verifier.notifications.MockListener.REASON_LISTENER_CANCEL;
 
 import android.annotation.SuppressLint;
+import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
@@ -75,7 +76,7 @@
 
 public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
         implements Runnable {
-    private static final String TAG = "NoListenerVerifier";
+    static final String TAG = "NoListenerVerifier";
     private static final String NOTIFICATION_CHANNEL_ID = TAG;
     private static final String NOISY_NOTIFICATION_CHANNEL_ID = TAG + "Noisy";
     protected static final String PREFS = "listener_prefs";
@@ -144,6 +145,9 @@
         tests.add(new IsDisabledTest());
         tests.add(new ServiceStoppedTest());
         tests.add(new NotificationNotReceivedTest());
+        tests.add(new AddScreenLockTest());
+        tests.add(new SecureActionOnLockScreenTest());
+        tests.add(new RemoveScreenLockTest());
         return tests;
     }
 
@@ -1608,4 +1612,118 @@
             next();
         }
     }
+
+    private class AddScreenLockTest extends InteractiveTestCase {
+        private View mView;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.add_screen_lock);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            status = READY;
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            KeyguardManager km = getSystemService(KeyguardManager.class);
+            if (km.isDeviceSecure()) {
+                status = PASS;
+            } else {
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_SECURITY_SETTINGS)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        }
+    }
+
+    private class SecureActionOnLockScreenTest extends InteractiveTestCase {
+        @Override
+        protected void setUp() {
+            createChannels();
+            ActionTriggeredReceiver.sendNotification(mContext, true);
+            status = READY;
+        }
+
+        @Override
+        protected void tearDown() {
+            mNm.cancelAll();
+            deleteChannels();
+            delay();
+        }
+
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createPassFailItem(parent, R.string.secure_action_lockscreen);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
+
+    private class RemoveScreenLockTest extends InteractiveTestCase {
+        private View mView;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.remove_screen_lock);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            status = READY;
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            KeyguardManager km = getSystemService(KeyguardManager.class);
+            if (!km.isDeviceSecure()) {
+                status = PASS;
+            } else {
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_SECURITY_SETTINGS)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/speech/tts/TtsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/speech/tts/TtsTestActivity.java
new file mode 100644
index 0000000..f95162d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/speech/tts/TtsTestActivity.java
@@ -0,0 +1,39 @@
+package com.android.cts.verifier.speech.tts;
+
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.View;
+import android.widget.Button;
+import androidx.annotation.Nullable;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+/**
+ * Guide the user to run test for the TTS API.
+ */
+public class TtsTestActivity extends PassFailButtons.Activity {
+
+  private Button mAccessibilitySettingsButton;
+
+  @Override
+  protected void onCreate(@Nullable Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    setContentView(R.layout.tts_main);
+    setInfoResources(R.string.tts_test, R.string.tts_test_info, -1);
+    setPassFailButtonClickListeners();
+
+    mAccessibilitySettingsButton = findViewById(R.id.accessibility_settings_button);
+    mAccessibilitySettingsButton.setOnClickListener(new View.OnClickListener() {
+        public void onClick(View v) {
+            try {
+                startActivityForResult(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0);
+            } catch (ActivityNotFoundException e) {}
+        }
+    });
+  }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
index 229ce0a..d79b0f3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
@@ -21,6 +21,7 @@
 import android.media.AudioAttributes;
 import android.media.AudioFormat;
 import android.media.AudioTrack;
+import android.view.View;
 
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.tv.TestSequence;
@@ -46,23 +47,20 @@
  *   <li>Receiver or soundbar is connected
  * </ol>
  */
-public class AudioCapabilitiesTestActivity extends TvAppVerifierActivity {
+public class AudioCapabilitiesTestActivity extends TvAppVerifierActivity
+        implements View.OnClickListener {
     private static final ImmutableList<AudioFormat> TESTED_AUDIO_FORMATS =
             ImmutableList.of(
                     // PCM formats
+                    makeAudioFormat(ENCODING_PCM_16BIT, 44100, 2),
                     makeAudioFormat(ENCODING_PCM_16BIT, 44100, 6),
-                    makeAudioFormat(ENCODING_PCM_16BIT, 44100, 8),
-
-                    // AC3 formats
-                    makeAudioFormat(ENCODING_AC3, 44100, 6),
-                    makeAudioFormat(ENCODING_AC3, 44100, 8),
 
                     // EAC3_JOC formats
-                    makeAudioFormat(ENCODING_E_AC3_JOC, 44100, 8),
-                    // EAC3 formats
-                    makeAudioFormat(ENCODING_E_AC3, 44100, 8));
+                    makeAudioFormat(ENCODING_E_AC3_JOC, 44100, 8));
 
     private TestSequence mTestSequence;
+    private View mSupportDolbyAtmosYesItem;
+    private View mSupportDolbyAtmosNoItem;
 
     @Override
     protected void setInfoResources() {
@@ -71,13 +69,36 @@
     }
 
     @Override
+    public void onClick(View v) {
+        if (containsButton(mSupportDolbyAtmosYesItem, v)) {
+            setPassState(mSupportDolbyAtmosYesItem, true);
+            setButtonEnabled(mSupportDolbyAtmosNoItem, false);
+            List<TestStepBase> testSteps = new ArrayList<>();
+            testSteps.add(new TvTestStep(this));
+            testSteps.add(new ReceiverTestStep(this));
+            testSteps.add(new TvTestStep(this));
+            mTestSequence = new TestSequence(this, testSteps);
+            mTestSequence.init();
+            return;
+        } else if (containsButton(mSupportDolbyAtmosNoItem, v)) {
+            setPassState(mSupportDolbyAtmosYesItem, true);
+            setButtonEnabled(mSupportDolbyAtmosNoItem, false);
+            List<TestStepBase> testSteps = new ArrayList<>();
+            testSteps.add(new TvTestStep(this));
+            mTestSequence = new TestSequence(this, testSteps);
+            mTestSequence.init();
+            return;
+        }
+    }
+
+    @Override
     protected void createTestItems() {
-        List<TestStepBase> testSteps = new ArrayList<>();
-        testSteps.add(new TvTestStep(this));
-        testSteps.add(new ReceiverTestStep(this));
-        testSteps.add(new TvTestStep(this));
-        mTestSequence = new TestSequence(this, testSteps);
-        mTestSequence.init();
+        mSupportDolbyAtmosYesItem =
+                createUserItem(
+                        R.string.tv_audio_capabilities_atmos_supported, R.string.tv_yes, this);
+        setButtonEnabled(mSupportDolbyAtmosYesItem, true);
+        mSupportDolbyAtmosNoItem = createButtonItem(R.string.tv_no, this);
+        setButtonEnabled(mSupportDolbyAtmosNoItem, true);
     }
 
     private class TvTestStep extends TestStep {
@@ -107,9 +128,7 @@
                     .withMessage("AudioTrack.isDirectPlaybackSupported only returns true for these")
                     .that(actualAudioFormatStrings.build())
                     .containsExactlyElementsIn(
-                            ImmutableList.of(
-                                    toStr(makeAudioFormat(ENCODING_PCM_16BIT, 44100, 6)),
-                                    toStr(makeAudioFormat(ENCODING_AC3, 44100, 6))));
+                            ImmutableList.of(toStr(makeAudioFormat(ENCODING_PCM_16BIT, 44100, 2))));
         }
     }
 
@@ -142,10 +161,8 @@
                     .that(actualAudioFormatStrings.build())
                     .containsExactlyElementsIn(
                             ImmutableList.of(
-                                    toStr(makeAudioFormat(ENCODING_PCM_16BIT, 41000, 8)),
-                                    toStr(makeAudioFormat(ENCODING_AC3, 44100, 8)),
-                                    toStr(makeAudioFormat(ENCODING_E_AC3_JOC, 44100, 8)),
-                                    toStr(makeAudioFormat(ENCODING_E_AC3, 44100, 8))));
+                                    toStr(makeAudioFormat(ENCODING_PCM_16BIT, 41000, 6)),
+                                    toStr(makeAudioFormat(ENCODING_E_AC3_JOC, 44100, 8))));
         }
     }
 
@@ -166,10 +183,6 @@
     /** Returns a displayable String message for {@code encodingCode}. */
     private static String encodingToString(int encodingCode) {
         switch (encodingCode) {
-            case ENCODING_AC3:
-                return "AC3";
-            case ENCODING_E_AC3:
-                return "E_AC3";
             case ENCODING_E_AC3_JOC:
                 return "E_AC3_JOC";
             case ENCODING_PCM_16BIT:
@@ -193,4 +206,8 @@
                 .setChannelMask(channelCountToMask(channelCount))
                 .build();
     }
+
+    private static boolean containsButton(View item, View button) {
+        return item == null ? false : item.findViewById(R.id.user_action_button) == button;
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayHdrCapabilitiesTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayHdrCapabilitiesTestActivity.java
index 72a34ba..e6128d8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayHdrCapabilitiesTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayHdrCapabilitiesTestActivity.java
@@ -69,12 +69,154 @@
     @Override
     protected void createTestItems() {
         List<TestStepBase> testSteps = new ArrayList<>();
-        testSteps.add(new TvPanelReportedTypesAreSupportedTestStep(this));
-        testSteps.add(new TvPanelSupportedTypesAreReportedTestStep(this));
+        if (TvUtil.isHdmiSourceDevice()) {
+            // The device is a set-top box or a TV dongle
+            testSteps.add(new NonHdrDisplayTestStep(this));
+            testSteps.add(new HdrDisplayTestStep(this));
+            testSteps.add(new NoDisplayTestStep(this));
+        } else {
+            // The device is a TV Panel
+            testSteps.add(new TvPanelReportedTypesAreSupportedTestStep(this));
+            testSteps.add(new TvPanelSupportedTypesAreReportedTestStep(this));
+        }
         mTestSequence = new TestSequence(this, testSteps);
         mTestSequence.init();
     }
 
+    private static class NonHdrDisplayTestStep extends SyncTestStep {
+
+        public NonHdrDisplayTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_hdr_capabilities_test_step_non_hdr_display,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_hdr_connect_no_hdr_display, context.getString(getButtonStringId()));
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTest() {
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+            getAsserter().withMessage("Display.isHdr()").that(display.isHdr()).isFalse();
+            getAsserter()
+                    .withMessage("Display.getHdrCapabilities()")
+                    .that(display.getHdrCapabilities().getSupportedHdrTypes())
+                    .isEmpty();
+        }
+    }
+
+    private static class HdrDisplayTestStep extends SyncTestStep {
+
+        public HdrDisplayTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_hdr_capabilities_test_step_hdr_display,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_hdr_connect_hdr_display, context.getString(getButtonStringId()));
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTest() {
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+
+            getAsserter().withMessage("Display.isHdr()").that(display.isHdr()).isTrue();
+
+            Display.HdrCapabilities hdrCapabilities = display.getHdrCapabilities();
+
+            int[] supportedHdrTypes = hdrCapabilities.getSupportedHdrTypes();
+            Arrays.sort(supportedHdrTypes);
+
+            getAsserter()
+                    .withMessage("Display.getHdrCapabilities().getSupportedTypes()")
+                    .that(supportedHdrTypes)
+                    .isEqualTo(
+                            new int[] {
+                                Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION,
+                                Display.HdrCapabilities.HDR_TYPE_HDR10,
+                                Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS,
+                                Display.HdrCapabilities.HDR_TYPE_HLG
+                            });
+
+            float maxLuminance = hdrCapabilities.getDesiredMaxLuminance();
+            getAsserter()
+                    .withMessage("Display.getHdrCapabilities().getDesiredMaxLuminance()")
+                    .that(maxLuminance)
+                    .isIn(Range.openClosed(0f, MAX_EXPECTED_LUMINANCE));
+
+            float minLuminance = hdrCapabilities.getDesiredMinLuminance();
+            getAsserter()
+                    .withMessage("Display.getHdrCapabilities().getDesiredMinLuminance()")
+                    .that(minLuminance)
+                    .isIn(Range.closedOpen(0f, MAX_EXPECTED_LUMINANCE));
+
+            getAsserter()
+                    .withMessage("Display.getHdrCapabilities().getDesiredMaxAverageLuminance()")
+                    .that(hdrCapabilities.getDesiredMaxAverageLuminance())
+                    .isIn(Range.openClosed(minLuminance, maxLuminance));
+        }
+    }
+
+    private static class NoDisplayTestStep extends AsyncTestStep {
+        public NoDisplayTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_hdr_capabilities_test_step_no_display,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_hdr_disconnect_display,
+                    context.getString(getButtonStringId()),
+                    DISPLAY_DISCONNECT_WAIT_TIME_SECONDS,
+                    DISPLAY_DISCONNECT_WAIT_TIME_SECONDS + 1);
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTestAsync() {
+            // Wait for the user to disconnect the display.
+            final long delay = Duration.ofSeconds(DISPLAY_DISCONNECT_WAIT_TIME_SECONDS).toMillis();
+            mContext.getPostTarget().postDelayed(this::runTest, delay);
+        }
+
+        private void runTest() {
+            try {
+                // Verify the display APIs do not crash when the display is disconnected
+                DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+                Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+                display.isHdr();
+                display.getHdrCapabilities();
+            } catch (Exception e) {
+                getAsserter().withMessage(Throwables.getStackTraceAsString(e)).fail();
+            }
+            done();
+        }
+    }
+
     private static class TvPanelReportedTypesAreSupportedTestStep extends YesNoTestStep {
         public TvPanelReportedTypesAreSupportedTestStep(TvAppVerifierActivity context) {
             super(context, getInstructionText(context), R.string.tv_yes, R.string.tv_no);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayModesTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayModesTestActivity.java
new file mode 100644
index 0000000..f5daa71
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayModesTestActivity.java
@@ -0,0 +1,331 @@
+/*
+ * 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.cts.verifier.tv.display;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.view.Display;
+
+import androidx.annotation.StringRes;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.tv.TestSequence;
+import com.android.cts.verifier.tv.TestStepBase;
+import com.android.cts.verifier.tv.TvAppVerifierActivity;
+import com.android.cts.verifier.tv.TvUtil;
+
+import com.google.common.base.Throwables;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+/**
+ * Test for verifying that the platform correctly reports display resolution and refresh rate. More
+ * specifically Display.getMode() and Display.getSupportedModes() APIs are tested. In the case for
+ * set-top boxes and TV dongles they are tested against reference displays. For TV panels they are
+ * tested against the hardware capabilities of the device.
+ */
+public class DisplayModesTestActivity extends TvAppVerifierActivity {
+    private static final int DISPLAY_DISCONNECT_WAIT_TIME_SECONDS = 5;
+    private static final float REFRESH_RATE_PRECISION = 0.01f;
+
+    private static final Subject.Factory<ModeSubject, Display.Mode> MODE_SUBJECT_FACTORY =
+            (failureMetadata, mode) -> new ModeSubject(failureMetadata, mode);
+
+    private static final Correspondence<Display.Mode, Mode> MODE_CORRESPONDENCE =
+            Correspondence.from((Display.Mode displayMode, Mode mode) -> {
+                return mode.isEquivalent(displayMode, REFRESH_RATE_PRECISION);
+            }, "is equivalent to");
+
+    private TestSequence mTestSequence;
+
+    @Override
+    protected void setInfoResources() {
+        setInfoResources(R.string.tv_display_modes_test, R.string.tv_display_modes_test_info, -1);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void createTestItems() {
+        List<TestStepBase> testSteps = new ArrayList<>();
+        if (TvUtil.isHdmiSourceDevice()) {
+            // The device is a set-top box or a TV dongle
+            testSteps.add(new NoDisplayTestStep(this));
+            testSteps.add(new Display2160pTestStep(this));
+            testSteps.add(new Display1080pTestStep(this));
+        } else {
+            // The device is a TV Panel
+            testSteps.add(new TvPanelReportedModesAreSupportedTestStep(this));
+            testSteps.add(new TvPanelSupportedModesAreReportedTestStep(this));
+        }
+        mTestSequence = new TestSequence(this, testSteps);
+        mTestSequence.init();
+    }
+
+    @Override
+    public String getTestDetails() {
+        return mTestSequence.getFailureDetails();
+    }
+
+    private static class NoDisplayTestStep extends AsyncTestStep {
+        public NoDisplayTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_display_modes_test_step_no_display,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_display_modes_disconnect_display,
+                    context.getString(getButtonStringId()),
+                    DISPLAY_DISCONNECT_WAIT_TIME_SECONDS,
+                    DISPLAY_DISCONNECT_WAIT_TIME_SECONDS + 1);
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTestAsync() {
+            final long delay = Duration.ofSeconds(DISPLAY_DISCONNECT_WAIT_TIME_SECONDS).toMillis();
+            mContext.getPostTarget().postDelayed(this::runTest, delay);
+        }
+
+        private void runTest() {
+            try {
+                // Verify the display APIs do not crash when the display is disconnected
+                DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+                Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+                display.getMode();
+                display.getSupportedModes();
+            } catch (Exception e) {
+                getAsserter().withMessage(Throwables.getStackTraceAsString(e)).fail();
+            }
+            done();
+        }
+    }
+
+    private static class Display2160pTestStep extends SyncTestStep {
+        public Display2160pTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_display_modes_test_step_2160p,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_display_modes_connect_2160p_display,
+                    context.getString(getButtonStringId()));
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTest() {
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+            getAsserter()
+                    .withMessage("Display.getMode()")
+                    .about(MODE_SUBJECT_FACTORY)
+                    .that(display.getMode())
+                    .isEquivalentToAnyOf(
+                            REFRESH_RATE_PRECISION,
+                            new Mode(3840, 2160, 60f),
+                            new Mode(3840, 2160, 50f));
+
+            Mode[] expected2160pSupportedModes =
+                    new Mode[] {
+                        new Mode(720, 480, 60f),
+                        new Mode(720, 576, 50f),
+                        // 720p modes
+                        new Mode(1280, 720, 50f),
+                        new Mode(1280, 720, 60f),
+                        // 1080p modes
+                        new Mode(1920, 1080, 24f),
+                        new Mode(1920, 1080, 25f),
+                        new Mode(1920, 1080, 30f),
+                        new Mode(1920, 1080, 50f),
+                        new Mode(1920, 1080, 60f),
+                        // 2160p modes
+                        new Mode(3840, 2160, 24f),
+                        new Mode(3840, 2160, 25f),
+                        new Mode(3840, 2160, 30f),
+                        new Mode(3840, 2160, 50f),
+                        new Mode(3840, 2160, 60f)
+                    };
+            getAsserter()
+                    .withMessage("Display.getSupportedModes()")
+                    .that(Arrays.asList(display.getSupportedModes()))
+                    .comparingElementsUsing(MODE_CORRESPONDENCE)
+                    .containsAtLeastElementsIn(expected2160pSupportedModes);
+        }
+    }
+
+    private static class Display1080pTestStep extends SyncTestStep {
+        public Display1080pTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_display_modes_test_step_1080p,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_display_modes_connect_1080p_display,
+                    context.getString(getButtonStringId()));
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTest() {
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+
+            getAsserter()
+                    .withMessage("Display.getMode()")
+                    .about(MODE_SUBJECT_FACTORY)
+                    .that(display.getMode())
+                    .isEquivalentToAnyOf(
+                            REFRESH_RATE_PRECISION,
+                            new Mode(1920, 1080, 60f),
+                            new Mode(1920, 1080, 50f));
+
+            final Mode[] expected1080pSupportedModes =
+                    new Mode[] {
+                        new Mode(720, 480, 60f),
+                        new Mode(720, 576, 50f),
+                        // 720p modes
+                        new Mode(1280, 720, 50f),
+                        new Mode(1280, 720, 60f),
+                        // 1080p modes
+                        new Mode(1920, 1080, 24f),
+                        new Mode(1920, 1080, 25f),
+                        new Mode(1920, 1080, 30f),
+                        new Mode(1920, 1080, 50f),
+                        new Mode(1920, 1080, 60f),
+                    };
+            getAsserter()
+                    .withMessage("Display.getSupportedModes()")
+                    .that(Arrays.asList(display.getSupportedModes()))
+                    .comparingElementsUsing(MODE_CORRESPONDENCE)
+                    .containsAtLeastElementsIn(expected1080pSupportedModes);
+        }
+    }
+
+    private static class TvPanelReportedModesAreSupportedTestStep extends YesNoTestStep {
+        public TvPanelReportedModesAreSupportedTestStep(TvAppVerifierActivity context) {
+            super(context, getInstructionText(context), R.string.tv_yes, R.string.tv_no);
+        }
+
+        private static String getInstructionText(Context context) {
+            DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+            String supportedModes =
+                    Arrays.stream(display.getSupportedModes())
+                            .map(DisplayModesTestActivity::formatDisplayMode)
+                            .collect(Collectors.joining("\n"));
+
+            return context.getString(
+                    R.string.tv_panel_display_modes_reported_are_supported, supportedModes);
+        }
+    }
+
+    private static class TvPanelSupportedModesAreReportedTestStep extends YesNoTestStep {
+        public TvPanelSupportedModesAreReportedTestStep(TvAppVerifierActivity context) {
+            super(context, getInstructionText(context), R.string.tv_no, R.string.tv_yes);
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(R.string.tv_panel_display_modes_supported_are_reported);
+        }
+    }
+
+    // We use a custom Mode class since the constructors of Display.Mode are hidden. Additionally,
+    // we want to use fuzzy comparison for frame rates which is not used in Display.Mode.equals().
+    private static class Mode {
+        public int mWidth;
+        public int mHeight;
+        public float mRefreshRate;
+
+        public Mode(int width, int height, float refreshRate) {
+            this.mWidth = width;
+            this.mHeight = height;
+            this.mRefreshRate = refreshRate;
+        }
+
+        public boolean isEquivalent(Display.Mode displayMode, float refreshRatePrecision) {
+            return mHeight == displayMode.getPhysicalHeight()
+                    && mWidth == displayMode.getPhysicalWidth()
+                    && Math.abs(mRefreshRate - displayMode.getRefreshRate()) < refreshRatePrecision;
+        }
+
+        @Override
+        public String toString() {
+            return formatDisplayMode(mWidth, mHeight, mRefreshRate);
+        }
+    }
+
+    private static class ModeSubject extends Subject<ModeSubject, Display.Mode> {
+        private final Display.Mode mActual;
+
+        public ModeSubject(FailureMetadata failureMetadata, @Nullable Display.Mode subject) {
+            super(failureMetadata, subject);
+            mActual = subject;
+        }
+
+        public void isEquivalentToAnyOf(final float refreshRatePrecision, Mode... modes) {
+            boolean found = Arrays.stream(modes)
+                    .anyMatch(mode -> mode.isEquivalent(mActual, refreshRatePrecision));
+            if (!found) {
+                failWithActual("expected any of", Arrays.toString(modes));
+            }
+        }
+    }
+
+    private static String formatDisplayMode(Display.Mode mode) {
+        return formatDisplayMode(
+                mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate());
+    }
+
+    private static String formatDisplayMode(int width, int height, float refreshRate) {
+        return String.format("%dx%d %.2f Hz", width, height, refreshRate);
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java
new file mode 100644
index 0000000..72b1209
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.common;
+
+public class BuilderBase {
+    // API Types - enumerated in high nibble
+    protected static final int TYPE_MASK = 0xF000;
+    private static final int TYPE_UNDEFINED = 0xF000;
+    public static final int TYPE_JAVA = 0x0000;
+    public static final int TYPE_OBOE = 0x1000;
+
+    // API subtypes - enumerated in low nibble
+    public static final int SUB_TYPE_MASK = 0x0000F;
+    public static final int SUB_TYPE_OBOE_DEFAULT = 0x0000;
+    public static final int SUB_TYPE_OBOE_AAUDIO = 0x0001;
+    public static final int SUB_TYPE_OBOE_OPENSL_ES = 0x0002;
+
+    protected int mType = TYPE_UNDEFINED;
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java
new file mode 100644
index 0000000..41dcad6
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.common;
+
+import android.media.AudioFormat;
+
+public abstract class StreamBase {
+    /*
+     * Stream attributes
+     */
+    protected int mChannelCount;
+    protected int mSampleRate;
+
+    public int getChannelCount() { return mChannelCount; }
+    public int getSampleRate() { return mSampleRate; }
+
+    public abstract int getNumBufferFrames();
+
+    // the thread on which the underlying Android AudioTrack/AudioRecord will run
+    protected Thread mStreamThread = null;
+
+    //
+    // Attributes
+    //
+
+    //
+    // Sample Format Utils
+    //
+    /**
+     * @param encoding An Android ENCODING_ constant for audio data.
+     * @return The size in BYTES of samples encoded as specified.
+     */
+    public static int sampleSizeInBytes(int encoding) {
+        switch (encoding) {
+            case AudioFormat.ENCODING_PCM_16BIT:
+                return 2;
+
+            case AudioFormat.ENCODING_PCM_FLOAT:
+                return 4;
+
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * @param numChannels   The number of channels in a FRAME of audio data.
+     * @return  The size in BYTES of a FRAME of audio data encoded as specified.
+     */
+    public static int calcFrameSizeInBytes(int numChannels) {
+        return sampleSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT) * numChannels;
+    }
+
+    //
+    // State
+    //
+
+    /**
+     * @param channelCount  The number of channels of audio data to be streamed.
+     * @param sampleRate    The stream sample rate
+     * @param numFrames     The number of frames of audio data in the stream's buffer.
+     * @return              True if the stream is successfully initialized.
+     */
+    public abstract boolean setupAudioStream(int channelCount, int sampleRate, int numFrames);
+
+    public abstract void teardownAudioStream();
+
+    /**
+     * Starts playback on an open stream player. (@see open() method above).
+     * @return <code>true</code> if playback/recording starts.
+     */
+    public abstract boolean startStream();
+
+    /**
+     * Stops playback.
+     * May not stop the stream immediately. i.e. does not stop until the next audio callback
+     * from the underlying system.
+     */
+    public abstract void stopStream();
+
+    //
+    // Thread stuff
+    //
+    /**
+     * Joins the record thread to ensure that the stream is stopped.
+     */
+    protected void waitForStreamThreadToExit() {
+        try {
+            if (mStreamThread != null) {
+                mStreamThread.join();
+                mStreamThread = null;
+            }
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    //
+    // Utility
+    //
+    /**
+     * @param chanCount The number of channels for which to generate an index mask.
+     * @return  A channel index mask corresponding to the supplied channel count.
+     *
+     * @note The generated index mask has active channels from 0 to chanCount - 1
+     */
+    public static int channelCountToIndexMask(int chanCount) {
+        return  (1 << chanCount) - 1;
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java
new file mode 100644
index 0000000..c1dc373
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.player;
+
+public interface AudioSource {
+    /**
+     * reset a stream to the beginning.
+     */
+    public void reset();
+
+    /**
+     * Process a request for audio data.
+     * @param audioData The buffer to be filled.
+     * @param numFrames The number of frames of audio to provide.
+     * @param numChans The number of channels (in the buffer) required by the player.
+     * @return The number of frames actually generated. If this value is less than that
+     * requested, it may be interpreted by the player as the end of playback.
+     * Note that the player will be blocked by this call.
+     * Note that the data is assumed to be *interleaved*.
+     */
+    public int pull(float[] audioData, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java
new file mode 100644
index 0000000..8a04686
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.player;
+
+public interface AudioSourceProvider {
+    /**
+     * @return return a Java AudioSource subclass object corresponding to the AudioSourceProvider
+     * implementation.
+     */
+    AudioSource getJavaSource();
+
+    /**
+     * @return a native (C/C++) AudioSource subclass object corresponding to the AudioSourceProvider
+     * implementation (stored in a long).
+     */
+    long getNativeSource();
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java
new file mode 100644
index 0000000..19f3fd9
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.player;
+
+import android.media.AudioFormat;
+import android.media.AudioTrack;
+import android.util.Log;
+
+import org.hyphonate.megaaudio.common.StreamBase;
+
+/**
+ * Implementation of abstract Player class implemented for the Android Java-based audio playback
+ * API, i.e. AudioTrack.
+ */
+public class JavaPlayer extends Player {
+    @SuppressWarnings("unused") private static String TAG = JavaPlayer.class.getSimpleName();
+    @SuppressWarnings("unused") private static final boolean LOG = false;
+
+    /*
+     * Player infrastructure
+     */
+    /* The AudioTrack for playing the audio stream */
+    private AudioTrack mAudioTrack;
+
+    private AudioSource mAudioSource;
+
+    // Playback state
+    /** <code>true</code> if currently playing audio data */
+    private boolean mPlaying;
+
+    /*
+     * Data buffers
+     */
+    /** Number of FRAMES of audio data in a burst buffer */
+    private int mNumBufferFrames;
+
+    /** The Burst Buffer. This is the buffer we fill with audio and feed into the AudioTrack. */
+    private float[] mAudioBuffer;
+
+    // Player-specific extension
+    public AudioTrack getAudioTrack() { return mAudioTrack; }
+
+    public JavaPlayer(AudioSourceProvider sourceProvider) {
+        super(sourceProvider);
+        mNumBufferFrames = -1;   // TODO need error defines
+    }
+
+    //
+    // Status
+    //
+    public boolean isPlaying() {
+        return mPlaying;
+    }
+
+    /**
+     * Allocates the array or the burst buffer.
+     */
+    private void allocBurstBuffer() {
+        // pad it by 1 frame. This allows some sources to not have to worry about
+        // handling the end-of-buffer edge case. i.e. a "Guard Point" for interpolation.
+        mAudioBuffer = new float[(mNumBufferFrames + 1) * mChannelCount];
+    }
+
+    /**
+     * @return The number of frames of audio data contained in the internal buffer.
+     */
+    @Override
+    public int getNumBufferFrames() {
+        return mNumBufferFrames;
+    }
+
+    /*
+     * State
+     */
+    @Override
+    public boolean setupAudioStream(int channelCount, int sampleRate, int numBufferFrames) {
+        if (LOG) {
+            Log.i(TAG, "setupAudioStream(chans:" + channelCount + ", rate:" + sampleRate +
+                    ", frames:" + numBufferFrames);
+        }
+
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+        mNumBufferFrames = numBufferFrames;
+
+        mAudioSource = mSourceProvider.getJavaSource();
+
+        try {
+            int bufferSizeInBytes = mNumBufferFrames * mChannelCount
+                    * sampleSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT);
+            mAudioTrack = new AudioTrack.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
+                            .setSampleRate(mSampleRate)
+                            .setChannelIndexMask(StreamBase.channelCountToIndexMask(mChannelCount))
+                            // .setChannelMask(channelMask)
+                            .build())
+                    .setBufferSizeInBytes(bufferSizeInBytes)
+                    .build();
+
+            allocBurstBuffer();
+        }  catch (UnsupportedOperationException ex) {
+            if (LOG) {
+                Log.i(TAG, "Couldn't open AudioTrack: " + ex);
+            }
+            mAudioTrack = null;
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void teardownAudioStream() {
+        stopStream();
+
+        waitForStreamThreadToExit();
+
+        if (mAudioTrack != null) {
+            mAudioTrack.release();
+            mAudioTrack = null;
+        }
+
+        mChannelCount = 0;
+        mSampleRate = 0;
+    }
+
+    /**
+     * Allocates the underlying AudioTrack and begins Playback.
+     * @return True if the stream is successfully started.
+     *
+     * This method returns when the start operation is complete, but before the first
+     * call to the AudioSource.pull() method.
+     */
+    @Override
+    public boolean startStream() {
+        waitForStreamThreadToExit(); // just to be sure.
+
+        mStreamThread = new Thread(new StreamPlayerRunnable(), "StreamPlayer Thread");
+        mPlaying = true;
+        mStreamThread.start();
+
+        return true;
+    }
+
+    /**
+     * Marks the stream for stopping on the next callback from the underlying system.
+     *
+     * Returns immediately, though a call to AudioSource.pull() may be in progress.
+     */
+    @Override
+    public void stopStream() {
+        mPlaying = false;
+    }
+
+    //
+    // StreamPlayerRunnable
+    //
+    /**
+     * Implements the <code>run</code> method for the playback thread.
+     * Gets initial audio data and starts the AudioTrack. Then continuously provides audio data
+     * until the flag <code>mPlaying</code> is set to false (in the stop() method).
+     */
+    private class StreamPlayerRunnable implements Runnable {
+        @Override
+        public void run() {
+            final int numBufferSamples = mNumBufferFrames * mChannelCount;
+
+            mAudioTrack.play();
+            while (mPlaying) {
+                mAudioSource.pull(mAudioBuffer, mNumBufferFrames, mChannelCount);
+
+                int numSamplesWritten = mAudioTrack.write(
+                        mAudioBuffer,0, numBufferSamples, AudioTrack.WRITE_BLOCKING);
+                if (numSamplesWritten < 0) {
+                    // error
+                    if (LOG) {
+                        Log.i(TAG, "AudioTrack write error: " + numSamplesWritten);
+                    }
+                    stopStream();
+                } else if (numSamplesWritten < numBufferSamples) {
+                    // end of stream
+                    if (LOG) {
+                        Log.i(TAG, "Stream Complete.");
+                    }
+                    stopStream();
+                }
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java
new file mode 100644
index 0000000..6419989
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.player;
+
+import android.media.AudioFormat;
+import android.media.AudioTrack;
+
+import org.hyphonate.megaaudio.common.StreamBase;
+
+/**
+ * An abstract class defining the common operations and attributes for all
+ * player (concrete) sub-classes.
+ */
+public abstract class Player extends StreamBase {
+    public Player(AudioSourceProvider sourceProvider) {
+        mSourceProvider = sourceProvider;
+    }
+
+    /*
+     * Audio Source
+     */
+    protected AudioSourceProvider mSourceProvider;
+
+    //
+    // Attributes
+    //
+    // This needs to be static because it is called before creating the Recorder subclass
+    public static int calcMinBufferFrames(int channelCount, int sampleRate) {
+        int channelMask = Player.channelCountToChannelMask(channelCount);
+        int bufferSizeInBytes =
+                AudioTrack.getMinBufferSize (sampleRate,
+                        channelMask,
+                        AudioFormat.ENCODING_PCM_FLOAT);
+        return bufferSizeInBytes / sampleSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT);
+    }
+
+    //
+    // Status
+    //
+    public abstract boolean isPlaying();
+
+    /*
+     * Channel utils
+     */
+    // TODO Consider moving these to a "Utility" library.
+    /**
+     * @param channelCount  The number of channels for which to generate an output position mask.
+     * @return An output channel-position mask corresponding to the supplied number of channels.
+     */
+    public static int channelCountToChannelMask(int channelCount) {
+        switch (channelCount) {
+            case 1:
+                return AudioFormat.CHANNEL_OUT_MONO;
+
+            case 2:
+                return AudioFormat.CHANNEL_OUT_STEREO;
+
+            case 3:
+                return AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
+
+            case 4:
+                return AudioFormat.CHANNEL_OUT_QUAD;
+
+            case 5: // 5.0
+                return AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
+
+            case 6: // 5.1
+                return AudioFormat.CHANNEL_OUT_5POINT1;
+
+            case 7: // 6.1
+                return AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
+
+            case 8:
+                return AudioFormat.CHANNEL_OUT_7POINT1;
+
+
+            default:
+                return AudioTrack.ERROR_BAD_VALUE;
+        }
+    }
+
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java
new file mode 100644
index 0000000..b32aa8c
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.player;
+
+import org.hyphonate.megaaudio.common.BuilderBase;
+
+public class PlayerBuilder extends BuilderBase {
+    private AudioSourceProvider mSourceProvider;
+
+    public PlayerBuilder() {
+
+    }
+
+    public PlayerBuilder setPlayerType(int playerType) {
+        mType = playerType;
+        return this;
+    }
+
+    public PlayerBuilder setSourceProvider(AudioSourceProvider sourceProvider) {
+        mSourceProvider = sourceProvider;
+        return this;
+    }
+
+    public Player build() throws BadStateException {
+        if (mSourceProvider == null) {
+            throw new BadStateException();
+        }
+
+        Player player = null;
+        int playerType = mType & TYPE_MASK;
+        switch (playerType) {
+            case TYPE_JAVA:
+                player = new JavaPlayer(mSourceProvider);
+                break;
+            // FIXME - Uncomment this code when the oboe-based implementation is integrated.
+//            case TYPE_OBOE:{
+//                int playerSubType = mType & SUB_TYPE_MASK;
+//                player = new OboePlayer(mSourceProvider, playerSubType);
+//            }
+//            break;
+
+            default:
+                throw new BadStateException();
+        }
+
+        return player;
+    }
+
+    public class BadStateException extends Throwable {
+
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSource.java
new file mode 100644
index 0000000..f8d803d
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSource.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.player.sources;
+
+public class SinAudioSource extends WaveTableSource {
+
+    /**
+     * The number of SAMPLES in the Sin Wave table.
+     * This is plenty of samples for a clear sine wave.
+     * the + 1 is to avoid special handling of the interpolation on the last sample.
+     */
+    static final int WAVETABLE_LENGTH = 2049;
+
+    public SinAudioSource() {
+        super();
+        float[] waveTbl = new float[WAVETABLE_LENGTH];
+        WaveTableSource.genSinWave(waveTbl);
+
+        super.setWaveTable(waveTbl);
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java
new file mode 100644
index 0000000..c674a5d
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.player.sources;
+
+import org.hyphonate.megaaudio.player.AudioSource;
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+
+public class SinAudioSourceProvider implements AudioSourceProvider {
+    @Override
+    public AudioSource getJavaSource() {
+        return new SinAudioSource();
+    }
+
+    @Override
+    public native long getNativeSource();
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java
new file mode 100644
index 0000000..4e4b89b
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.player.sources;
+
+import org.hyphonate.megaaudio.player.AudioSource;
+
+/**
+ * An AudioFiller implementation for feeding data from a PCMFLOAT wavetable.
+ * We do simple, linear interpolation for inter-table values.
+ */
+public class WaveTableSource implements AudioSource {
+    @SuppressWarnings("unused") private static String TAG = WaveTableSource.class.getSimpleName();
+
+    /** The samples defining one cycle of the waveform to play */
+    protected float[] mWaveTbl;
+    /** The number of samples in the wave table. Note that the wave table is presumed to contain
+     * an "extra" sample (a copy of the 1st sample) in order to simplify the interpolation
+     * calculation. Thus, this value will be 1 less than the length of mWaveTbl.
+     */
+    protected int mNumWaveTblSamples;
+    /** The phase (offset within the wave table) of the next output sample.
+     *  Note that this may (will) be a fractional value. Range 0.0 -> mNumWaveTblSamples.
+     */
+    protected float mSrcPhase;
+    /** The sample rate at which playback occurs */
+    protected float mSampleRate = 48000;  // This seems likely, but can be changed
+    /** The frequency of the generated audio signal */
+    protected float mFreq = 1000;         // Some reasonable default frequency
+    /** The "Nominal" frequency of the wavetable. i.e., the frequency that would be generated if
+     * each sample in the wave table was sent in turn to the output at the specified sample rate.
+     */
+    protected float mFN;
+    /** 1 / mFN. Calculated when mFN is set to avoid a division on each call to fill() */
+    protected float mFNInverse;
+
+    /**
+     * Constructor.
+     */
+    public WaveTableSource() {
+    }
+
+    /**
+     * Calculates the "Nominal" frequency of the wave table.
+     */
+    private void calcFN() {
+        mFN = mSampleRate / (float)mNumWaveTblSamples;
+        mFNInverse = 1.0f / mFN;
+    }
+
+    /**
+     * Sets up to play samples from the provided wave table.
+     * @param waveTbl Contains the samples defining a single cycle of the desired waveform.
+     *                This wave table contains a redundant sample in the last slot (== first slot)
+     *                to make the interpolation calculation simpler, so the logical length of
+     *                the wave table is one less than the length of the array.
+     */
+    public void setWaveTable(float[] waveTbl) {
+        mWaveTbl = waveTbl;
+        mNumWaveTblSamples = waveTbl != null ? mWaveTbl.length - 1 : 0;
+
+        calcFN();
+    }
+
+    /**
+     * Sets the playback sample rate for which samples will be generated.
+     * @param sampleRate
+     */
+    public void setSampleRate(float sampleRate) {
+        mSampleRate = sampleRate;
+        calcFN();
+    }
+
+    /**
+     * Set the frequency of the output signal.
+     * @param freq  Signal frequency in Hz.
+     */
+    public void setFreq(float freq) {
+        mFreq = freq;
+    }
+
+    /**
+     * Resets the playback position to the 1st sample.
+     */
+    @Override
+    public void reset() {
+        mSrcPhase = 0.0f;
+    }
+
+    /**
+     * Fills the specified buffer with values generated from the wave table which will playback
+     * at the specified frequency.
+     *
+     * @param buffer The buffer to be filled.
+     * @param numFrames The number of frames of audio to provide.
+     * @param numChans The number of channels (in the buffer) required by the player.
+     * @return  The number of samples generated. Since we are generating a continuous periodic
+     * signal, this will always be <code>numFrames</code>.
+     */
+    @Override
+    public int pull(float[] buffer, int numFrames, int numChans) {
+        final float phaseIncr = mFreq * mFNInverse;
+        int outIndex = 0;
+        for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+            // 'mod' back into the waveTable
+            while (mSrcPhase >= (float)mNumWaveTblSamples) {
+                mSrcPhase -= (float)mNumWaveTblSamples;
+            }
+
+            // linear-interpolate
+            int srcIndex = (int)mSrcPhase;
+            float delta0 = mSrcPhase - (float)srcIndex;
+            float delta1 = 1.0f - delta0;
+            float value = ((mWaveTbl[srcIndex] * delta0) + (mWaveTbl[srcIndex + 1] * delta1));
+
+            // Put the same value in all channels.
+            for (int chanIndex = 0; chanIndex < numChans; chanIndex++) {
+                buffer[outIndex++] = value;
+            }
+
+            mSrcPhase += phaseIncr;
+        }
+
+        return numFrames;
+    }
+
+    /*
+     * Standard wavetable generators
+     */
+    static public void genSinWave(float[] buffer) {
+        int size = buffer.length;
+        float incr = ((float)Math.PI  * 2.0f) / (float)(size - 1);
+        for(int index = 0; index < size; index++) {
+            buffer[index] = (float)Math.sin(index * incr);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java
new file mode 100644
index 0000000..a94d997
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder;
+
+public abstract class AudioSink {
+    public void start() {}
+    public void stop(int lastBufferFrames) {}
+
+    /**
+     * Process incoming audio data.
+     * @param audioData The buffer to be filled.
+     * @param numFrames The number of frames of audio to provide.
+     * @param numChans The number of channels (in the buffer) required by the player.
+     * Note that the recorder will be blocked by this call.
+     * Note that the data is assumed to be *interleaved*.
+     */
+    abstract public void process(final float[] audioData, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java
new file mode 100644
index 0000000..558b4ac
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder;
+
+public interface AudioSinkProvider {
+    AudioSink getJavaSink();
+    long getOboeSink();
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java
new file mode 100644
index 0000000..4257953
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder;
+
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import org.hyphonate.megaaudio.common.StreamBase;
+import org.hyphonate.megaaudio.recorder.sinks.NopAudioSinkProvider;
+
+/**
+ * Implementation of abstract Recorder class implemented for the Android Java-based audio record
+ * API, i.e. AudioRecord.
+ */
+public class JavaRecorder extends Recorder {
+    @SuppressWarnings("unused") private static String TAG = JavaRecorder.class.getSimpleName();
+    @SuppressWarnings("unused") private static final boolean LOG = true;
+
+    /** The buffer to receive the recorder samples */
+    private float[] mRecorderBuffer;
+
+    /** The number of FRAMES of audio data in the record buffer */
+    private int mNumBuffFrames;
+
+    // Recording state
+    /** <code>true</code> if currently recording audio data */
+    private boolean mRecording = false;
+
+    /* The AudioRecord for recording the audio stream */
+    private AudioRecord mAudioRecord = null;
+
+    private AudioSink mAudioSink;
+
+    /**
+     * The listener to receive notifications of recording events
+     * @see {@link JavaSinkHandler}
+     */
+    private JavaSinkHandler mListener = null;
+
+    public JavaRecorder(AudioSinkProvider sinkProvider) {
+        super(sinkProvider);
+    }
+
+    /** The buff to receive the recorder samples */
+    public float[] getFloatBuffer() { return mRecorderBuffer; }
+
+    // JavaRecorder-specific extension
+    public AudioRecord getAudioRecord() { return mAudioRecord; }
+
+    /*
+     * State
+     */
+    @Override
+    public boolean isRecording() {
+        return mRecording;
+    }
+
+    @Override
+    public boolean setupAudioStream(int channelCount, int sampleRate, int numBurstFrames) {
+        if (LOG) {
+            Log.i(TAG, "setupAudioStream(chans:" + channelCount + ", rate:" + sampleRate +
+                    ", frames:" + numBurstFrames);
+        }
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+
+        try {
+            int frameSize = calcFrameSizeInBytes(mChannelCount);
+
+            mAudioRecord = new AudioRecord.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
+                            .setSampleRate(mSampleRate)
+                            .setChannelIndexMask(StreamBase.channelCountToIndexMask(mChannelCount))
+                            .build())
+                    .setBufferSizeInBytes(numBurstFrames * frameSize)
+                    .build();
+
+            mNumBuffFrames = mAudioRecord.getBufferSizeInFrames();
+
+            mRecorderBuffer = new float[mNumBuffFrames * mChannelCount];
+
+            if (mSinkProvider == null) {
+                mSinkProvider = new NopAudioSinkProvider();
+            }
+            mAudioSink = mSinkProvider.getJavaSink();
+            mListener = new JavaSinkHandler(this, mAudioSink, Looper.getMainLooper());
+            return true;
+        } catch (UnsupportedOperationException ex) {
+            if (LOG) {
+                Log.i(TAG, "Couldn't open AudioRecord: " + ex);
+            }
+            mAudioRecord = null;
+            mNumBuffFrames = 0;
+            mRecorderBuffer = null;
+
+            return false;
+        }
+    }
+
+    @Override
+    public void teardownAudioStream() {
+        stopStream();
+
+        waitForStreamThreadToExit();
+
+        if (mAudioRecord != null) {
+            mAudioRecord.release();
+            mAudioRecord = null;
+        }
+
+        mChannelCount = 0;
+        mSampleRate = 0;
+    }
+
+    @Override
+    public boolean startStream() {
+
+        // Routing
+//        mAudioRecord.setPreferredDevice(mRoutingDevice);
+//
+        if (mListener != null) {
+            mListener.sendEmptyMessage(JavaSinkHandler.MSG_START);
+        }
+
+        try {
+            mAudioRecord.startRecording();
+        } catch (IllegalStateException ex) {
+            Log.e(TAG, "startRecording exception: " + ex);
+        }
+
+        waitForStreamThreadToExit(); // just to be sure.
+
+        mStreamThread = new Thread(new RecorderRunnable(), "JavaRecorder Thread");
+        mRecording = true;
+        mStreamThread.start();
+
+        return true;
+    }
+
+    /**
+     * Marks the stream for stopping on the next callback from the underlying system.
+     *
+     * Returns immediately, though a call to AudioSource.push() may be in progress.
+     */
+    @Override
+    public void stopStream() {
+        mRecording = false;
+    }
+
+    // @Override
+    // Used in JavaSinkHandler
+    public float[] getDataBuffer() {
+        return mRecorderBuffer;
+        // System.arraycopy(mRecorderBuffer, 0, buffer, 0, mNumBuffFrames * mChannelCount);
+    }
+
+    @Override
+    public int getNumBufferFrames() {
+        return mNumBuffFrames;
+    }
+
+    /*
+     * Recorder Thread
+     */
+    /**
+     * Implements the <code>run</code> method for the record thread.
+     * Starts the AudioRecord, then continuously reads audio data
+     * until the flag <code>mRecording</code> is set to false (in the stop() method).
+     */
+    private class RecorderRunnable implements Runnable {
+        @Override
+        public void run() {
+            final int numBurstSamples = mNumBuffFrames * mChannelCount;
+            int numReadSamples = 0;
+            while (mRecording) {
+                numReadSamples = mAudioRecord.read(
+                        mRecorderBuffer, 0, numBurstSamples, AudioRecord.READ_BLOCKING);
+
+                if (numReadSamples < 0) {
+                    // error
+                    if (LOG) {
+                        Log.e(TAG, "AudioRecord write error: " + numReadSamples);
+                    }
+                    stopStream();
+                } else if (numReadSamples < numBurstSamples) {
+                    // got less than requested?
+                    if (LOG) {
+                        Log.e(TAG, "AudioRecord Underflow: " + numReadSamples +
+                                " vs. " + numBurstSamples);
+                    }
+                    stopStream();
+                }
+
+                if (mListener != null) {
+                    // TODO: on error or underrun we may be send bogus data.
+                    mListener.sendEmptyMessage(JavaSinkHandler.MSG_BUFFER_FILL);
+                }
+            }
+
+            if (mListener != null) {
+                // TODO: on error or underrun we may be send bogus data.
+                Message message = new Message();
+                message.what = JavaSinkHandler.MSG_STOP;
+                message.arg1 = numReadSamples;
+                mListener.sendMessage(message);
+            }
+            mAudioRecord.stop();
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java
new file mode 100644
index 0000000..1e0bb3b
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.hyphonate.megaaudio.recorder;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * Defines a super-class for apps to receive notifications of recording events. Concrete
+ * subclasses need to implement the <code>handleMessage(Message)</code> method.
+ */
+public class JavaSinkHandler extends Handler {
+    @SuppressWarnings("unused") private static final String TAG = JavaSinkHandler.class.getSimpleName();
+    @SuppressWarnings("unused") private static final boolean LOG = false;
+
+    protected JavaRecorder mRecorder;
+
+    private AudioSink mSink = null;
+
+    public JavaSinkHandler(JavaRecorder recorder, AudioSink sink, Looper looper) {
+        super(looper);
+        mRecorder = recorder;
+        mSink = sink;
+    }
+
+    /**
+     * Recording Event IDs.
+     */
+    /** Sent when recording has started */
+    public static final int MSG_START = 0;
+    /** Sent when a recording buffer has been filled */
+    public static final int MSG_BUFFER_FILL = 1;
+    /** Sent when recording has been stopped */
+    public static final int MSG_STOP = 2;
+
+    @Override
+    public void handleMessage (Message msg) {
+        switch (msg.what) {
+            case MSG_START:
+                if (mSink != null) {
+                    mSink.start();
+                }
+                break;
+
+            case MSG_BUFFER_FILL:
+                if (mSink != null) {
+                    mSink.process(mRecorder.getDataBuffer(),
+                            mRecorder.getNumBufferFrames(), mRecorder.getChannelCount());
+                }
+                break;
+
+            case MSG_STOP:
+                if (mSink != null) {
+                    // arg1 has the number of samples from the last read.
+                    mSink.stop(msg.arg1);
+                }
+                break;
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java
new file mode 100644
index 0000000..fff28d6
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder;
+
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+
+import org.hyphonate.megaaudio.common.StreamBase;
+
+public abstract class Recorder extends StreamBase {
+    protected AudioSinkProvider mSinkProvider;
+
+    public Recorder(AudioSinkProvider sinkProvider) {
+        mSinkProvider = sinkProvider;
+    }
+
+    /*
+     * State
+     */
+    public abstract boolean isRecording();
+
+    /*
+     * Utilities
+     */
+    public static final int AUDIO_CHANNEL_COUNT_MAX = 30;
+
+    public static final int AUDIO_CHANNEL_REPRESENTATION_POSITION   = 0x0;
+    public static final int AUDIO_CHANNEL_REPRESENTATION_INDEX      = 0x2;
+
+    //
+    // Attributes
+    //
+    // This needs to be static because it is called before creating the Recorder subclass
+    public static int calcMinBufferFrames(int channelCount, int sampleRate) {
+        int channelMask = Recorder.channelCountToChannelMask(channelCount);
+        int bufferSizeInBytes =
+                AudioRecord.getMinBufferSize (sampleRate,
+                        channelMask,
+                        AudioFormat.ENCODING_PCM_FLOAT);
+        return bufferSizeInBytes / sampleSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT);
+    }
+
+    /*
+     * Channel Utils
+     */
+    // TODO - Consider moving these into a "Utilities" library
+//    /**
+//     * @param chanCount The number of channels for which to generate an index mask.
+//     * @return  A channel index mask corresponding to the supplied channel count.
+//     *
+//     * @note The generated index mask has active channels from 0 to chanCount - 1
+//     */
+//    public static int countToIndexMask(int chanCount) {
+//        return  (1 << chanCount) - 1;
+//    }
+
+    /* Not part of public API */
+    private static int audioChannelMaskFromRepresentationAndBits(int representation, int bits)
+    {
+        return ((representation << AUDIO_CHANNEL_COUNT_MAX) | bits);
+    }
+
+    /* Derive a channel mask for index assignment from a channel count.
+     * Returns the matching channel mask,
+     * or AUDIO_CHANNEL_NONE if the channel count is zero,
+     * or AUDIO_CHANNEL_INVALID if the channel count exceeds AUDIO_CHANNEL_COUNT_MAX.
+     */
+    private static int audioChannelMaskForIndexAssignmentFromCount(int channel_count)
+    {
+        if (channel_count == 0) {
+            return 0; // AUDIO_CHANNEL_NONE
+        }
+        if (channel_count > AUDIO_CHANNEL_COUNT_MAX) {
+            return AudioFormat.CHANNEL_INVALID;
+        }
+        int bits = (1 << channel_count) - 1;
+        return audioChannelMaskFromRepresentationAndBits(AUDIO_CHANNEL_REPRESENTATION_INDEX, bits);
+    }
+
+    /**
+     * @param channelCount  The number of channels for which to generate an input position mask.
+     * @return An input channel-position mask corresponding the supplied number of channels.
+     */
+    public static int channelCountToChannelMask(int channelCount) {
+        int bits;
+        switch (channelCount) {
+            case 1:
+                bits = AudioFormat.CHANNEL_IN_MONO;
+                break;
+
+            case 2:
+                bits = AudioFormat.CHANNEL_IN_STEREO;
+                break;
+
+            case 3:
+            case 4:
+            case 5:
+            case 6:
+            case 7:
+            case 8:
+                // FIXME FCC_8
+                return audioChannelMaskForIndexAssignmentFromCount(channelCount);
+
+            default:
+                return AudioFormat.CHANNEL_INVALID;
+        }
+
+        return audioChannelMaskFromRepresentationAndBits(AUDIO_CHANNEL_REPRESENTATION_POSITION, bits);
+    }
+
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java
new file mode 100644
index 0000000..78234da
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder;
+
+import org.hyphonate.megaaudio.common.BuilderBase;
+
+public class RecorderBuilder extends BuilderBase {
+
+    private AudioSinkProvider mSinkProvider;
+
+    public RecorderBuilder() {
+
+    }
+
+    public RecorderBuilder setRecorderType(int type) {
+        mType = type;
+        return this;
+    }
+
+    public RecorderBuilder setAudioSinkProvider(AudioSinkProvider sinkProvider) {
+        mSinkProvider = sinkProvider;
+        return this;
+    }
+
+    public Recorder build() throws BadStateException {
+        if (mSinkProvider == null) {
+            throw new BadStateException();
+        }
+
+        Recorder recorder = null;
+        int playerType = mType & TYPE_MASK;
+        switch (playerType) {
+            case TYPE_JAVA:
+                recorder = new JavaRecorder(mSinkProvider);
+                break;
+
+            // FIXME - Uncomment this code when the oboe-based implementation is integrated.
+//            case TYPE_OBOE:{
+//                int recorderSubType = mType & SUB_TYPE_MASK;
+//                recorder = new OboeRecorder(mSinkProvider, recorderSubType);
+//            }
+//            break;
+
+            default:
+                throw new BadStateException();
+        }
+
+        return recorder;
+    }
+
+    public class BadStateException extends Throwable {
+
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallback.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallback.java
new file mode 100644
index 0000000..3c7e7be
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallback.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder.sinks;
+
+public interface AppCallback {
+    void onDataReady(float[] audioData, int numFrames);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java
new file mode 100644
index 0000000..e39a13f
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder.sinks;
+
+import org.hyphonate.megaaudio.recorder.AudioSink;
+
+public class AppCallbackAudioSink extends AudioSink {
+    private static final String TAG = AppCallbackAudioSink.class.getSimpleName();
+
+    private AppCallback mCallback;
+
+    public AppCallbackAudioSink(AppCallback callback) {
+        mCallback = callback;
+    }
+
+    @Override
+    public void process(float[] audioData, int numFrames, int numChans) {
+        mCallback.onDataReady(audioData, numFrames);
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java
new file mode 100644
index 0000000..867bfce
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder.sinks;
+
+import org.hyphonate.megaaudio.recorder.AudioSink;
+import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
+
+public class AppCallbackAudioSinkProvider implements AudioSinkProvider {
+    private AppCallback mCallbackObj;
+    private long mOboeSinkObj;
+
+    public AppCallbackAudioSinkProvider(AppCallback callback) {
+        mCallbackObj = callback;
+    }
+
+    public AudioSink getJavaSink() {
+        return new AppCallbackAudioSink(mCallbackObj);
+    }
+
+    @Override
+    public long getOboeSink() {
+        return mOboeSinkObj = getOboeSinkN(mCallbackObj);
+    }
+
+    private native long getOboeSinkN(AppCallback callbackObj);
+
+    public void releaseJNIResources() {
+        releaseJNIResourcesN(mOboeSinkObj);
+    }
+
+    private native void releaseJNIResourcesN(long oboeSink);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java
new file mode 100644
index 0000000..9d41141
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder.sinks;
+
+import org.hyphonate.megaaudio.recorder.AudioSink;
+
+public class NopAudioSink extends AudioSink {
+    @Override
+    public void process(float[] audioData, int numFrames, int numChannels) {
+        // NOP
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java
new file mode 100644
index 0000000..6a2d584
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder.sinks;
+
+import org.hyphonate.megaaudio.recorder.AudioSink;
+import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
+
+public class NopAudioSinkProvider implements AudioSinkProvider {
+    @Override
+    public AudioSink getJavaSink() {
+        return new NopAudioSink();
+    }
+
+    @Override
+    public long getOboeSink() {
+        return 0;
+    }
+}
diff --git a/apps/CtsVerifierUSBCompanion/AndroidManifest.xml b/apps/CtsVerifierUSBCompanion/AndroidManifest.xml
index 594f4ee..e6bcb79 100644
--- a/apps/CtsVerifierUSBCompanion/AndroidManifest.xml
+++ b/apps/CtsVerifierUSBCompanion/AndroidManifest.xml
@@ -16,32 +16,35 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.verifierusbcompanion">
+     package="com.android.cts.verifierusbcompanion">
 
-    <uses-sdk android:minSdkVersion="12" android:targetSdkVersion="25" />
+    <uses-sdk android:minSdkVersion="12"
+         android:targetSdkVersion="25"/>
 
-    <uses-feature android:name="android.hardware.usb.accessory" />
-    <uses-feature android:name="android.hardware.usb.host" />
+    <uses-feature android:name="android.hardware.usb.accessory"/>
+    <uses-feature android:name="android.hardware.usb.host"/>
 
     <application android:label="@string/app_name"
-            android:icon="@drawable/icon">
+         android:icon="@drawable/icon">
 
         <activity android:name=".Main"
-                android:screenOrientation="portrait"
-                android:configChanges="orientation|keyboardHidden">
+             android:screenOrientation="portrait"
+             android:configChanges="orientation|keyboardHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name=".AccessoryAttachmentHandler">
+        <activity android:name=".AccessoryAttachmentHandler"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
+                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"/>
             </intent-filter>
 
             <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
-                    android:resource="@xml/accessory_filter" />
+                 android:resource="@xml/accessory_filter"/>
         </activity>
     </application>
 </manifest>
diff --git a/apps/EmptyDeviceAdmin/AndroidManifest.xml b/apps/EmptyDeviceAdmin/AndroidManifest.xml
index 2ee9422..3683f9e 100644
--- a/apps/EmptyDeviceAdmin/AndroidManifest.xml
+++ b/apps/EmptyDeviceAdmin/AndroidManifest.xml
@@ -15,19 +15,18 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.emptydeviceadmin" >
+     package="com.android.cts.emptydeviceadmin">
 
     <uses-sdk android:minSdkVersion="12"/>
 
     <application android:label="Test Device Admin">
-        <receiver
-            android:name=".EmptyDeviceAdmin"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
-            <meta-data
-                android:name="android.app.device_admin"
-                android:resource="@xml/device_admin"/>
+        <receiver android:name=".EmptyDeviceAdmin"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
+            <meta-data android:name="android.app.device_admin"
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
diff --git a/apps/EmptyDeviceOwner/AndroidManifest.xml b/apps/EmptyDeviceOwner/AndroidManifest.xml
index d6a97eb..7cb6d45 100644
--- a/apps/EmptyDeviceOwner/AndroidManifest.xml
+++ b/apps/EmptyDeviceOwner/AndroidManifest.xml
@@ -15,23 +15,24 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.emptydeviceowner" >
+     package="com.android.cts.emptydeviceowner">
 
     <uses-sdk android:minSdkVersion="12"/>
 
-    <application android:label="Test Device Owner" android:testOnly="true">
-        <receiver
-            android:name=".EmptyDeviceAdmin"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
-            <meta-data
-                android:name="android.app.device_admin"
-                android:resource="@xml/device_admin"/>
+    <application android:label="Test Device Owner"
+         android:testOnly="true">
+        <receiver android:name=".EmptyDeviceAdmin"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
+            <meta-data android:name="android.app.device_admin"
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <receiver android:name=".DeviceOwnerChangedReceiver">
+        <receiver android:name=".DeviceOwnerChangedReceiver"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_OWNER_CHANGED"/>
             </intent-filter>
diff --git a/apps/MainlineModuleDetector/AndroidManifest.xml b/apps/MainlineModuleDetector/AndroidManifest.xml
index 4cc8f8c..dce1cae 100644
--- a/apps/MainlineModuleDetector/AndroidManifest.xml
+++ b/apps/MainlineModuleDetector/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?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");
@@ -16,15 +15,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.mainlinemoduledetector"
-          android:versionCode="1"
-          android:versionName="1.0">
+     package="com.android.cts.mainlinemoduledetector"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <application>
-        <activity android:name=".MainlineModuleDetector">
+        <activity android:name=".MainlineModuleDetector"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/apps/NotificationBot/AndroidManifest.xml b/apps/NotificationBot/AndroidManifest.xml
index 0388cbc..95d9178 100644
--- a/apps/NotificationBot/AndroidManifest.xml
+++ b/apps/NotificationBot/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!-- Copyright (C) 2010 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,31 +15,34 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="com.android.cts.robot"
-      android:versionCode="1"
-      android:versionName="1.0">
+     package="com.android.cts.robot"
+     android:versionCode="1"
+     android:versionName="1.0">
 
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="21"/>
+    <uses-sdk android:minSdkVersion="19"
+         android:targetSdkVersion="21"/>
 
     <application android:label="@string/app_name"
-            android:icon="@drawable/icon"
-            android:debuggable="true">
+         android:icon="@drawable/icon"
+         android:debuggable="true">
 
         <!-- Required because a bare service won't show up in the app notifications list. -->
-        <activity android:name=".NotificationBotActivity">
+        <activity android:name=".NotificationBotActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <!-- services used by the CtsVerifier to test notifications. -->
-        <receiver android:name=".NotificationBot" android:exported="true">
+        <receiver android:name=".NotificationBot"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.robot.ACTION_POST" />
-                <action android:name="com.android.cts.robot.ACTION_CANCEL" />
-                <action android:name="com.android.cts.robot.ACTION_RESET_SETUP_NOTIFICATION" />
-                <action android:name="com.android.cts.robot.ACTION_INLINE_REPLY" />
+                <action android:name="com.android.cts.robot.ACTION_POST"/>
+                <action android:name="com.android.cts.robot.ACTION_CANCEL"/>
+                <action android:name="com.android.cts.robot.ACTION_RESET_SETUP_NOTIFICATION"/>
+                <action android:name="com.android.cts.robot.ACTION_INLINE_REPLY"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/apps/OomCatcher/AndroidManifest.xml b/apps/OomCatcher/AndroidManifest.xml
index 25513e2..daa6fb3 100644
--- a/apps/OomCatcher/AndroidManifest.xml
+++ b/apps/OomCatcher/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +15,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="com.android.cts.oomcatcher"
-      android:versionCode="1"
-      android:versionName="1.0">
+     package="com.android.cts.oomcatcher"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <application>
-        <activity android:name=".OomCatcher">
+        <activity android:name=".OomCatcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/apps/PermissionApp/AndroidManifest.xml b/apps/PermissionApp/AndroidManifest.xml
index 41e5aaa..7e4c2ff 100644
--- a/apps/PermissionApp/AndroidManifest.xml
+++ b/apps/PermissionApp/AndroidManifest.xml
@@ -16,24 +16,24 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.permissionapp">
+     package="com.android.cts.permissionapp">
 
     <uses-sdk android:minSdkVersion="23"/>
 
     <permission android:name="com.android.cts.permissionapp.permA"
-                android:protectionLevel="dangerous"
-                android:label="@string/permA"
-                android:permissionGroup="com.android.cts.permissionapp.groupAB"
-                android:description="@string/permA" />
+         android:protectionLevel="dangerous"
+         android:label="@string/permA"
+         android:permissionGroup="com.android.cts.permissionapp.groupAB"
+         android:description="@string/permA"/>
     <permission android:name="com.android.cts.permissionapp.permB"
-                android:protectionLevel="dangerous"
-                android:label="@string/permB"
-                android:permissionGroup="com.android.cts.permissionapp.groupAB"
-                android:description="@string/permB" />
+         android:protectionLevel="dangerous"
+         android:label="@string/permB"
+         android:permissionGroup="com.android.cts.permissionapp.groupAB"
+         android:description="@string/permB"/>
 
     <permission-group android:description="@string/groupAB"
-                      android:label="@string/groupAB"
-                      android:name="com.android.cts.permissionapp.groupAB" />
+         android:label="@string/groupAB"
+         android:name="com.android.cts.permissionapp.groupAB"/>
 
     <uses-permission android:name="com.android.cts.permissionapp.permA"/>
     <uses-permission android:name="com.android.cts.permissionapp.permB"/>
@@ -45,15 +45,15 @@
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
 
     <application android:label="CtsPermissionApp"
-            android:icon="@drawable/ic_permissionapp">
-        <activity android:name=".PermissionActivity" >
+         android:icon="@drawable/ic_permissionapp">
+        <activity android:name=".PermissionActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.permission.action.CHECK_HAS_PERMISSION" />
-                <action android:name="com.android.cts.permission.action.REQUEST_PERMISSION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.permission.action.CHECK_HAS_PERMISSION"/>
+                <action android:name="com.android.cts.permission.action.REQUEST_PERMISSION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/apps/TtsTestApp/Android.bp b/apps/TtsTestApp/Android.bp
new file mode 100644
index 0000000..1270c5502
--- /dev/null
+++ b/apps/TtsTestApp/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsTtsEngineSelectorTestHelper",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    manifest: "AndroidManifest.xml",
+    sdk_version: "test_current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ]
+}
+
+android_test_helper_app {
+    name: "CtsTtsEngineSelectorTestHelper2",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    manifest: "AndroidManifest.xml",
+    sdk_version: "test_current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    aaptflags: [
+	"--rename-manifest-package com.google.android.cts.tts.helper2"
+    ]
+}
diff --git a/apps/TtsTestApp/AndroidManifest.xml b/apps/TtsTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..5fdac07
--- /dev/null
+++ b/apps/TtsTestApp/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.tts.helper">
+<application
+  android:label="TTS CTS Test Helper App">
+  <service
+    android:name=".TTSHelperService"
+    android:exported="true">
+    <intent-filter android:priority="100">
+      <action android:name="android.intent.action.TTS_SERVICE" />
+      <category android:name="android.intent.category.DEFAULT" />
+    </intent-filter>
+  </service>
+</application>
+</manifest>
diff --git a/apps/TtsTestApp/OWNERS b/apps/TtsTestApp/OWNERS
new file mode 100644
index 0000000..c984212
--- /dev/null
+++ b/apps/TtsTestApp/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 63521
+rni@google.com
+weiguo@google.com
+joshimbriani@google.com
diff --git a/apps/TtsTestApp/src/com/android/cts/tts/helper/TTSHelperService.java b/apps/TtsTestApp/src/com/android/cts/tts/helper/TTSHelperService.java
new file mode 100644
index 0000000..33ea454
--- /dev/null
+++ b/apps/TtsTestApp/src/com/android/cts/tts/helper/TTSHelperService.java
@@ -0,0 +1,39 @@
+package com.android.cts.tts.helper;
+
+import android.speech.tts.SynthesisCallback;
+import android.speech.tts.SynthesisRequest;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeechService;
+
+/**
+ * Stub implementation of TTS service
+ */
+public class TTSHelperService extends TextToSpeechService {
+
+  public TTSHelperService() {}
+
+  @Override
+  protected int onIsLanguageAvailable(String lang, String country, String variant) {
+    return TextToSpeech.LANG_AVAILABLE;
+  }
+
+  @Override
+  public int onLoadLanguage(String lang, String country, String variant) {
+    return TextToSpeech.LANG_AVAILABLE;
+  }
+
+  @Override
+  protected void onStop() {
+    return;
+  }
+
+  @Override
+  protected void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback) {
+    return;
+  }
+
+  @Override
+  protected String[] onGetLanguage() {
+    return new String[] {"", "", ""};
+  }
+}
diff --git a/apps/VpnApp/api23/AndroidManifest.xml b/apps/VpnApp/api23/AndroidManifest.xml
index cd2f19b..58b851f 100644
--- a/apps/VpnApp/api23/AndroidManifest.xml
+++ b/apps/VpnApp/api23/AndroidManifest.xml
@@ -15,13 +15,14 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.vpnfirewall">
+     package="com.android.cts.vpnfirewall">
 
     <uses-sdk android:targetSdkVersion="23"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 
     <application android:label="@string/app">
-        <activity android:name=".VpnClient">
+        <activity android:name=".VpnClient"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <action android:name="com.android.cts.vpnfirewall.action.CONNECT_AND_FINISH"/>
@@ -30,7 +31,8 @@
         </activity>
 
         <service android:name=".ReflectorVpnService"
-                android:permission="android.permission.BIND_VPN_SERVICE">
+             android:permission="android.permission.BIND_VPN_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.net.VpnService"/>
             </intent-filter>
diff --git a/apps/VpnApp/api24/AndroidManifest.xml b/apps/VpnApp/api24/AndroidManifest.xml
index 964e741..2787175 100644
--- a/apps/VpnApp/api24/AndroidManifest.xml
+++ b/apps/VpnApp/api24/AndroidManifest.xml
@@ -15,13 +15,14 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.vpnfirewall">
+     package="com.android.cts.vpnfirewall">
 
     <uses-sdk android:targetSdkVersion="24"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 
     <application android:label="@string/app">
-        <activity android:name=".VpnClient">
+        <activity android:name=".VpnClient"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <action android:name="com.android.cts.vpnfirewall.action.CONNECT_AND_FINISH"/>
@@ -30,9 +31,11 @@
         </activity>
 
         <service android:name=".ReflectorVpnService"
-                android:permission="android.permission.BIND_VPN_SERVICE">
+             android:permission="android.permission.BIND_VPN_SERVICE"
+             android:exported="true">
             <!-- Dummy entry below to test the default value of always-on opt-opt flag -->
-            <meta-data android:name="dummy-name" android:value="dummy-value"/>
+            <meta-data android:name="dummy-name"
+                 android:value="dummy-value"/>
             <intent-filter>
                 <action android:name="android.net.VpnService"/>
             </intent-filter>
diff --git a/apps/VpnApp/latest/AndroidManifest.xml b/apps/VpnApp/latest/AndroidManifest.xml
index 418726a..76c5e35 100644
--- a/apps/VpnApp/latest/AndroidManifest.xml
+++ b/apps/VpnApp/latest/AndroidManifest.xml
@@ -15,13 +15,14 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.vpnfirewall">
+     package="com.android.cts.vpnfirewall">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
     <application android:label="@string/app">
-        <activity android:name=".VpnClient">
+        <activity android:name=".VpnClient"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <action android:name="com.android.cts.vpnfirewall.action.CONNECT_AND_FINISH"/>
@@ -30,7 +31,8 @@
         </activity>
 
         <service android:name=".ReflectorVpnService"
-                android:permission="android.permission.BIND_VPN_SERVICE">
+             android:permission="android.permission.BIND_VPN_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.net.VpnService"/>
             </intent-filter>
diff --git a/apps/VpnApp/notalwayson/AndroidManifest.xml b/apps/VpnApp/notalwayson/AndroidManifest.xml
index 4b9184e..c165d08 100644
--- a/apps/VpnApp/notalwayson/AndroidManifest.xml
+++ b/apps/VpnApp/notalwayson/AndroidManifest.xml
@@ -15,13 +15,14 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.vpnfirewall">
+     package="com.android.cts.vpnfirewall">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
     <application android:label="@string/app">
-        <activity android:name=".VpnClient">
+        <activity android:name=".VpnClient"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <action android:name="com.android.cts.vpnfirewall.action.CONNECT_AND_FINISH"/>
@@ -30,12 +31,13 @@
         </activity>
 
         <service android:name=".ReflectorVpnService"
-                android:permission="android.permission.BIND_VPN_SERVICE">
+             android:permission="android.permission.BIND_VPN_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.net.VpnService"/>
             </intent-filter>
             <meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
-                       android:value="false"/>
+                 android:value="false"/>
         </service>
     </application>
 
diff --git a/apps/hotspot/AndroidManifest.xml b/apps/hotspot/AndroidManifest.xml
index 277be5f..09c494f 100644
--- a/apps/hotspot/AndroidManifest.xml
+++ b/apps/hotspot/AndroidManifest.xml
@@ -1,20 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.hotspot">
 
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.hotspot">
+
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <application>
-        <activity android:name=".MainActivity">
+        <activity android:name=".MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".Notify" android:exported="true">
+        <receiver android:name=".Notify"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.hotspot.TEST_ACTION" />
+                <action android:name="com.android.cts.hotspot.TEST_ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/common/device-side/util-axt/Android.bp b/common/device-side/util-axt/Android.bp
index ec36fe7..501eedb 100644
--- a/common/device-side/util-axt/Android.bp
+++ b/common/device-side/util-axt/Android.bp
@@ -25,6 +25,7 @@
     static_libs: [
         "compatibility-common-util-devicesidelib",
         "androidx.test.rules",
+        "androidx.test.ext.junit",
         "ub-uiautomator",
         "mockito-target-minus-junit4",
         "androidx.annotation_annotation",
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java
new file mode 100644
index 0000000..ee33f42
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java
@@ -0,0 +1,810 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public abstract class BaseDefaultPermissionGrantPolicyTest extends BusinessLogicTestCase {
+    public static final String LOG_TAG = "DefaultPermissionGrantPolicy";
+    private static final String PLATFORM_PACKAGE_NAME = "android";
+
+    private static final String BRAND_PROPERTY = "ro.product.brand";
+    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
+
+    private Set<DefaultPermissionGrantException> mRemoteExceptions = new HashSet<>();
+
+    /**
+     * Returns whether this build is a CN build.
+     */
+    public abstract boolean isCnBuild();
+
+    /**
+     * Returns whether this build is a CN build with GMS.
+     */
+    public abstract boolean isCnGmsBuild();
+
+    /**
+     * Add default permissions for all applicable apps.
+     */
+    public abstract void addDefaultSystemHandlerPermissions(
+            ArrayMap<String, PackageInfo> packagesToVerify,
+            SparseArray<UidState> pregrantUidStates) throws Exception;
+
+    /**
+     * Return the names of all the runtime permissions to check for violations.
+     */
+    public abstract Set<String> getRuntimePermissionNames(List<PackageInfo> packageInfos);
+
+    /**
+     * Returns whether the permission name, as defined in
+     * {@link PermissionManager.SplitPermissionInfo#getNewPermissions()}
+     * should be considered a violation.
+     */
+    public abstract boolean isSplitPermissionNameViolation(String permissionName);
+
+    public void testDefaultGrantsWithRemoteExceptions(boolean preGrantsOnly) throws Exception {
+        List<PackageInfo> allPackages = getAllPackages();
+        Set<String> runtimePermNames = getRuntimePermissionNames(allPackages);
+        ArrayMap<String, PackageInfo> packagesToVerify =
+                getMsdkTargetingPackagesUsingRuntimePerms(allPackages, runtimePermNames);
+
+        // Ignore CTS infrastructure
+        packagesToVerify.remove("android.tradefed.contentprovider");
+
+        SparseArray<UidState> pregrantUidStates = new SparseArray<>();
+
+        addDefaultSystemHandlerPermissions(packagesToVerify, pregrantUidStates);
+
+        // Add split permissions that were split from non-runtime permissions
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.Q)) {
+            addSplitFromNonDangerousPermissions(packagesToVerify, pregrantUidStates);
+        }
+
+        // Add exceptions
+        addExceptionsDefaultPermissions(packagesToVerify, runtimePermNames, pregrantUidStates);
+
+        // packageName -> message -> [permission]
+        ArrayMap<String, ArrayMap<String, ArraySet<String>>> violations = new ArrayMap();
+
+        // Enforce default grants in the right state
+        checkDefaultGrantsInCorrectState(packagesToVerify, pregrantUidStates, violations);
+
+        // Nothing else should have default grants
+        checkPackagesForUnexpectedGrants(packagesToVerify, runtimePermNames, violations,
+                preGrantsOnly);
+
+        logPregrantUidStates(pregrantUidStates);
+
+        // Bail if we found any violations
+        if (!violations.isEmpty()) {
+            fail(createViolationsErrorString(violations));
+        }
+    }
+
+
+    /**
+     * Primarily invoked by business logic, set default permission grant exceptions for this
+     * instance of the test class. This is an alternative to downloading the encrypted xml
+     * file, a process which is now deprecated.
+     *
+     * @param pkg         the package name
+     * @param sha256      the sha256 cert digest of the package
+     * @param permissions the set of permissions, formatted "permission_name fixed_boolean"
+     */
+    public void setException(String pkg, String sha256, String... permissions) {
+        HashMap<String, Boolean> permissionsMap = new HashMap<>();
+        for (String permissionString : permissions) {
+            String[] parts = permissionString.trim().split("\\s+");
+            if (parts.length != 2) {
+                Log.e(LOG_TAG, "Unable to parse remote exception permission: " + permissionString);
+                return;
+            }
+            permissionsMap.put(parts[0], Boolean.valueOf(parts[1]));
+        }
+        mRemoteExceptions.add(new DefaultPermissionGrantException(pkg, sha256, permissionsMap));
+    }
+
+    /**
+     * Primarily invoked by business logic, set default permission grant exceptions for this
+     * instance of the test class. Also enables the supply of exception metadata.
+     *
+     * @param company     the company name
+     * @param metadata    the exception metadata
+     * @param pkg         the package name
+     * @param sha256      the sha256 cert digest of the package
+     * @param permissions the set of permissions, formatted "permission_name fixed_boolean"
+     */
+    public void setExceptionWithMetadata(String company, String metadata, String pkg,
+            String sha256, String... permissions) {
+        HashMap<String, Boolean> permissionsMap = new HashMap<>();
+        for (String permissionString : permissions) {
+            String[] parts = permissionString.trim().split("\\s+");
+            if (parts.length != 2) {
+                Log.e(LOG_TAG, "Unable to parse remote exception permission: " + permissionString);
+                return;
+            }
+            permissionsMap.put(parts[0], Boolean.valueOf(parts[1]));
+        }
+        mRemoteExceptions.add(new DefaultPermissionGrantException(
+                company, metadata, pkg, sha256, permissionsMap));
+    }
+
+    /**
+     * Primarily invoked by business logic, set default permission grant exceptions on CN Gms
+     * for this instance of the test class. This is an alternative to downloading the encrypted
+     * xml file, a process which is now deprecated.
+     *
+     * @param pkg         the package name
+     * @param sha256      the sha256 cert digest of the package
+     * @param permissions the set of permissions, formatted "permission_name fixed_boolean"
+     */
+    public void setCNGmsException(String pkg, String sha256, String... permissions) {
+        if (!isCnGmsBuild()) {
+            Log.e(LOG_TAG, "Regular GMS build, skip allowlisting: " + pkg);
+            return;
+        }
+        setException(pkg, sha256, permissions);
+    }
+
+    /**
+     * Primarily invoked by business logic, set default permission grant exceptions on CN Gms
+     * for this instance of the test class. This is an alternative to downloading the encrypted
+     * xml file, a process which is now deprecated.
+     *
+     * @param company     the company name
+     * @param metadata    the exception metadata
+     * @param pkg         the package name
+     * @param sha256      the sha256 cert digest of the package
+     * @param permissions the set of permissions, formatted "permission_name fixed_boolean"
+     */
+    public void setCNGmsExceptionWithMetadata(String company, String metadata, String pkg,
+            String sha256, String... permissions) {
+        if (!isCnGmsBuild()) {
+            Log.e(LOG_TAG, "Regular GMS build, skip allowlisting: " + pkg);
+            return;
+        }
+        setExceptionWithMetadata(company, metadata, pkg, sha256, permissions);
+    }
+
+
+    public List<PackageInfo> getAllPackages() {
+        return getInstrumentation().getContext().getPackageManager()
+                .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES
+                        | PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);
+    }
+
+    public static ArrayMap<String, PackageInfo> getMsdkTargetingPackagesUsingRuntimePerms(
+            List<PackageInfo> packageInfos, Set<String> runtimePermNames) {
+        ArrayMap<String, PackageInfo> packageInfoMap = new ArrayMap<>();
+
+        final int packageInfoCount = packageInfos.size();
+        for (int i = 0; i < packageInfoCount; i++) {
+            PackageInfo packageInfo = packageInfos.get(i);
+            if (packageInfo.requestedPermissions == null) {
+                continue;
+            }
+            if (packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                continue;
+            }
+            for (String requestedPermission : packageInfo.requestedPermissions) {
+                if (runtimePermNames.contains(requestedPermission)) {
+                    packageInfoMap.put(packageInfo.packageName, packageInfo);
+                    break;
+                }
+            }
+        }
+
+        return packageInfoMap;
+    }
+
+    public static void addViolation(
+            Map<String, ArrayMap<String, ArraySet<String>>> violations, String packageName,
+            String permission, String message) {
+        if (!violations.containsKey(packageName)) {
+            violations.put(packageName, new ArrayMap<>());
+        }
+
+        if (!violations.get(packageName).containsKey(message)) {
+            violations.get(packageName).put(message, new ArraySet<>());
+        }
+
+        violations.get(packageName).get(message).add(permission);
+    }
+
+    public static boolean isPackageOnSystemImage(PackageInfo packageInfo) {
+        return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+
+    public static String computePackageCertDigest(Signature signature) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("SHA256");
+        } catch (NoSuchAlgorithmException e) {
+            /* can't happen */
+            return null;
+        }
+
+        messageDigest.update(signature.toByteArray());
+
+        final byte[] digest = messageDigest.digest();
+        final int digestLength = digest.length;
+        final int charCount = 2 * digestLength;
+
+        final char[] chars = new char[charCount];
+        for (int i = 0; i < digestLength; i++) {
+            final int byteHex = digest[i] & 0xFF;
+            chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
+            chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
+        }
+        return new String(chars);
+    }
+
+    public static ArrayMap<String, Object> getPackageProperties(String packageName) {
+        ArrayMap<String, Object> properties = new ArrayMap();
+
+        PackageManager pm = getInstrumentation().getContext().getPackageManager();
+        PackageInfo info = null;
+        try {
+            info = pm.getPackageInfo(packageName,
+                    PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_SIGNATURES);
+        } catch (PackageManager.NameNotFoundException ignored) {
+        }
+
+        properties.put("targetSDK", info.applicationInfo.targetSdkVersion);
+        properties.put("signature", computePackageCertDigest(info.signatures[0]).toUpperCase());
+        properties.put("uid", UserHandle.getAppId(info.applicationInfo.uid));
+        properties.put("priv app", info.applicationInfo.isPrivilegedApp());
+        properties.put("persistent", ((info.applicationInfo.flags
+                & ApplicationInfo.FLAG_PERSISTENT) != 0) + "\n");
+        properties.put("has platform signature", (pm.checkSignatures(info.packageName,
+                PLATFORM_PACKAGE_NAME) == PackageManager.SIGNATURE_MATCH));
+        properties.put("on system image", isPackageOnSystemImage(info));
+
+        return properties;
+    }
+
+
+    public void addException(DefaultPermissionGrantException exception,
+            Set<String> runtimePermNames, Map<String, PackageInfo> packageInfos,
+            SparseArray<UidState> outUidStates) {
+        Log.v(LOG_TAG, "Adding exception for company " + exception.company
+                + ". Metadata: " + exception.metadata);
+        String packageName = exception.pkg;
+        PackageInfo packageInfo = packageInfos.get(packageName);
+        if (packageInfo == null) {
+            Log.v(LOG_TAG, "Trying to add exception to missing package:" + packageName);
+
+            try {
+                // Do not overwhelm logging
+                Thread.sleep(10);
+            } catch (InterruptedException ignored) {
+            }
+            return;
+        }
+        if (!packageInfo.applicationInfo.isSystemApp()) {
+            if (isCnBuild() && exception.hasNonBrandSha256()) {
+                // Due to CN app removability requirement, allow non-system app pregrant exceptions,
+                // as long as they specify a hash (b/121209050)
+            } else {
+                Log.w(LOG_TAG, "Cannot pregrant permissions to non-system package:" + packageName);
+                return;
+            }
+        }
+        // If cert SHA-256 digest is specified it is used for verification, for user builds only
+        if (exception.hasNonBrandSha256()) {
+            String expectedDigest = exception.sha256.replace(":", "").toLowerCase();
+            String packageDigest = computePackageCertDigest(packageInfo.signatures[0]);
+            if (PropertyUtil.isUserBuild() && !expectedDigest.equalsIgnoreCase(packageDigest)) {
+                Log.w(LOG_TAG, "SHA256 cert digest does not match for package: " + packageName
+                        + ". Expected: " + expectedDigest.toUpperCase() + ", observed: "
+                        + packageDigest.toUpperCase());
+                return;
+            }
+        } else if (exception.hasBrand) {
+            // Rare case -- exception.sha256 is actually brand name, verify brand instead
+            String expectedBrand = exception.sha256;
+            String actualBrand = PropertyUtil.getProperty(BRAND_PROPERTY);
+            if (!expectedBrand.equalsIgnoreCase(actualBrand)) {
+                Log.w(LOG_TAG, String.format("Brand %s does not match for package: %s",
+                        expectedBrand, packageName));
+                return;
+            }
+        } else {
+            Log.w(LOG_TAG, "Attribute sha256-cert-digest or brand must be provided for package: "
+                    + packageName);
+            return;
+        }
+
+        List<String> requestedPermissions = Arrays.asList(packageInfo.requestedPermissions);
+        for (Map.Entry<String, Boolean> entry : exception.permissions.entrySet()) {
+            String permission = entry.getKey();
+            Boolean fixed = entry.getValue();
+            if (!requestedPermissions.contains(permission)) {
+                Log.w(LOG_TAG, "Permission " + permission + " not requested by: " + packageName);
+                continue;
+            }
+            if (!runtimePermNames.contains(permission)) {
+                Log.w(LOG_TAG, "Permission:" + permission + " in not runtime");
+                continue;
+            }
+            final int uid = packageInfo.applicationInfo.uid;
+            UidState uidState = outUidStates.get(uid);
+            if (uidState == null) {
+                uidState = new UidState();
+                outUidStates.put(uid, uidState);
+            }
+
+            for (String extendedPerm : extendBySplitPermissions(permission,
+                    packageInfo.applicationInfo.targetSdkVersion)) {
+                uidState.overrideGrantedPermission(packageInfo.packageName,
+                        permission.equals(extendedPerm) ? "exception"
+                                : "exception (split from " + permission + ")", extendedPerm, fixed);
+            }
+        }
+    }
+
+
+    public static ArrayList<String> extendBySplitPermissions(String permission, int appTargetSdk) {
+        ArrayList<String> extendedPermissions = new ArrayList<>();
+        extendedPermissions.add(permission);
+
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.Q)) {
+            Context context = getInstrumentation().getTargetContext();
+            PermissionManager permissionManager = context.getSystemService(PermissionManager.class);
+
+            for (PermissionManager.SplitPermissionInfo splitPerm :
+                    permissionManager.getSplitPermissions()) {
+                if (splitPerm.getSplitPermission().equals(permission)
+                        && splitPerm.getTargetSdk() > appTargetSdk) {
+                    extendedPermissions.addAll(splitPerm.getNewPermissions());
+                }
+            }
+        }
+
+        return extendedPermissions;
+    }
+
+
+    public void setPermissionGrantState(String packageName, String permission,
+            boolean granted) {
+        try {
+            if (granted) {
+                getInstrumentation().getUiAutomation().grantRuntimePermission(packageName,
+                        permission, android.os.Process.myUserHandle());
+            } else {
+                getInstrumentation().getUiAutomation().revokeRuntimePermission(packageName,
+                        permission, android.os.Process.myUserHandle());
+            }
+        } catch (Exception e) {
+            /* do nothing - best effort */
+        }
+    }
+
+    public void addExceptionsDefaultPermissions(Map<String, PackageInfo> packageInfos,
+            Set<String> runtimePermNames,
+            SparseArray<UidState> outUidStates) throws Exception {
+        // Only use exceptions from business logic if they've been added
+        if (!mRemoteExceptions.isEmpty()) {
+            Log.d(LOG_TAG, String.format("Found %d remote exceptions", mRemoteExceptions.size()));
+            for (DefaultPermissionGrantException dpge : mRemoteExceptions) {
+                addException(dpge, runtimePermNames, packageInfos, outUidStates);
+            }
+        } else {
+            Log.w(LOG_TAG, "Failed to retrieve remote default permission grant exceptions.");
+        }
+    }
+
+
+    // Permissions split from non dangerous permissions
+    private void addSplitFromNonDangerousPermissions(Map<String, PackageInfo> packageInfos,
+            SparseArray<UidState> outUidStates) {
+        Context context = getInstrumentation().getTargetContext();
+
+        for (PackageInfo pkg : packageInfos.values()) {
+            int targetSdk = pkg.applicationInfo.targetSandboxVersion;
+            int uid = pkg.applicationInfo.uid;
+
+            for (String permission : pkg.requestedPermissions) {
+                PermissionInfo permInfo;
+                try {
+                    permInfo = context.getPackageManager().getPermissionInfo(permission, 0);
+                } catch (PackageManager.NameNotFoundException ignored) {
+                    // ignore permissions that are requested but not defined
+                    continue;
+                }
+
+
+                // Permissions split from dangerous permission are granted when the original
+                // permission permission is granted;
+                if ((permInfo.getProtection() & PROTECTION_DANGEROUS) != 0) {
+                    continue;
+                }
+
+                for (String extendedPerm : extendBySplitPermissions(permission, targetSdk)) {
+                    if (!isSplitPermissionNameViolation(extendedPerm)) {
+                        continue;
+                    }
+
+                    if (!extendedPerm.equals(permission)) {
+                        UidState uidState = outUidStates.get(uid);
+
+                        if (uidState != null
+                                && uidState.grantedPermissions.containsKey(extendedPerm)) {
+                            // permission is already granted. Don't override the grant-state.
+                            continue;
+                        }
+
+                        appendPackagePregrantedPerms(pkg, "split from non-dangerous permission "
+                                        + permission, false, Collections.singleton(extendedPerm),
+                                outUidStates);
+                    }
+                }
+            }
+        }
+    }
+
+    public static void appendPackagePregrantedPerms(PackageInfo packageInfo, String reason,
+            boolean fixed, Set<String> pregrantedPerms, SparseArray<UidState> outUidStates) {
+        final int uid = packageInfo.applicationInfo.uid;
+        UidState uidState = outUidStates.get(uid);
+        if (uidState == null) {
+            uidState = new UidState();
+            outUidStates.put(uid, uidState);
+        }
+        for (String requestedPermission : packageInfo.requestedPermissions) {
+            if (pregrantedPerms.contains(requestedPermission)) {
+                uidState.addGrantedPermission(packageInfo.packageName, reason, requestedPermission,
+                        fixed);
+            }
+        }
+    }
+
+    public void logPregrantUidStates(SparseArray<UidState> pregrantUidStates) {
+        Log.i(LOG_TAG, "PREGRANT UID STATES");
+        for (int i = 0; i < pregrantUidStates.size(); i++) {
+            Log.i(LOG_TAG, "uid: " + pregrantUidStates.keyAt(i) + " {");
+            pregrantUidStates.valueAt(i).log();
+            Log.i(LOG_TAG, "}");
+        }
+    }
+
+    public void checkDefaultGrantsInCorrectState(Map<String, PackageInfo> packagesToVerify,
+            SparseArray<UidState> pregrantUidStates,
+            Map<String, ArrayMap<String, ArraySet<String>>> violations) {
+        PackageManager packageManager = getInstrumentation().getContext().getPackageManager();
+        for (PackageInfo packageInfo : packagesToVerify.values()) {
+            final int uid = packageInfo.applicationInfo.uid;
+            UidState uidState = pregrantUidStates.get(uid);
+            if (uidState == null) {
+                continue;
+            }
+
+            final int grantCount = uidState.grantedPermissions.size();
+            // make a modifiable list
+            List<String> requestedPermissions = new ArrayList<>(
+                    Arrays.asList(packageInfo.requestedPermissions));
+            for (int i = 0; i < grantCount; i++) {
+                String permission = uidState.grantedPermissions.keyAt(i);
+
+                // If the package did not request this permissions, skip as
+                // it is requested by another package in the same UID.
+                if (!requestedPermissions.contains(permission)) {
+                    continue;
+                }
+
+                // Not granting the permission is OK, as we want to catch excessive grants
+                if (packageManager.checkPermission(permission, packageInfo.packageName)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    continue;
+                }
+
+                boolean grantBackFineLocation = false;
+
+                // Special case: fine location implies coarse location, so we revoke
+                // fine location when verifying coarse to avoid interference.
+                if (permission.equals(Manifest.permission.ACCESS_COARSE_LOCATION)
+                        && packageManager.checkPermission(Manifest.permission.ACCESS_FINE_LOCATION,
+                        packageInfo.packageName) == PackageManager.PERMISSION_GRANTED) {
+                    setPermissionGrantState(packageInfo.packageName,
+                            Manifest.permission.ACCESS_FINE_LOCATION, false);
+                    grantBackFineLocation = true;
+                }
+
+                setPermissionGrantState(packageInfo.packageName, permission, false);
+
+                Boolean fixed = uidState.grantedPermissions.valueAt(i);
+
+                // Weaker grant is fine, e.g. not-fixed instead of fixed.
+                if (!fixed && packageManager.checkPermission(permission, packageInfo.packageName)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    addViolation(violations, packageInfo.packageName, permission,
+                            "granted by default should be revocable");
+                }
+
+                setPermissionGrantState(packageInfo.packageName, permission, true);
+
+                if (grantBackFineLocation) {
+                    setPermissionGrantState(packageInfo.packageName,
+                            Manifest.permission.ACCESS_FINE_LOCATION, true);
+                }
+
+                // Now a small trick - pretend the package does not request this permission
+                // as we will later treat each granted runtime permissions as a violation.
+                requestedPermissions.remove(permission);
+                packageInfo.requestedPermissions = requestedPermissions.toArray(
+                        new String[requestedPermissions.size()]);
+            }
+        }
+    }
+
+    public void checkPackagesForUnexpectedGrants(Map<String, PackageInfo> packagesToVerify,
+            Set<String> runtimePermNames,
+            Map<String, ArrayMap<String, ArraySet<String>>> violations,
+            boolean preGrantsOnly) throws Exception {
+        PackageManager packageManager = getInstrumentation().getContext().getPackageManager();
+        for (PackageInfo packageInfo : packagesToVerify.values()) {
+            for (String requestedPermission : packageInfo.requestedPermissions) {
+                // If another package in the UID can get the permission
+                // then it is fine for this package to have it - skip.
+                if (runtimePermNames.contains(requestedPermission)
+                        && packageManager.checkPermission(requestedPermission,
+                        packageInfo.packageName) == PackageManager.PERMISSION_GRANTED
+                        && (!preGrantsOnly || (callWithShellPermissionIdentity(() ->
+                        packageManager.getPermissionFlags(
+                                requestedPermission,
+                                packageInfo.packageName,
+                                getInstrumentation().getTargetContext().getUser())
+                                & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT)
+                        != 0))) {
+                    addViolation(violations,
+                            packageInfo.packageName, requestedPermission,
+                            "cannot be granted by default to package");
+                }
+            }
+        }
+    }
+
+    public String createViolationsErrorString(
+            ArrayMap<String, ArrayMap<String, ArraySet<String>>> violations) {
+        StringBuilder sb = new StringBuilder();
+
+        for (String packageName : violations.keySet()) {
+            sb.append("packageName: " + packageName + " {\n");
+            for (Map.Entry<String, Object> property
+                    : getPackageProperties(packageName).entrySet()) {
+                sb.append("  " + property.getKey() + ": "
+                        + property.getValue().toString().trim() + "\n");
+            }
+            for (String message : violations.get(packageName).keySet()) {
+                sb.append("  message: " + message + " {\n");
+                for (String permission : violations.get(packageName).get(message)) {
+                    sb.append("    permission: " + permission + "\n");
+                }
+                sb.append("  }\n");
+            }
+            sb.append("}\n");
+        }
+
+        return sb.toString();
+    }
+
+    public static class UidState {
+        public class GrantReason {
+            public final String reason;
+            public final boolean override;
+            public final Boolean fixed;
+
+            GrantReason(String reason, boolean override, Boolean fixed) {
+                this.reason = reason;
+                this.override = override;
+                this.fixed = fixed;
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) return true;
+                if (o == null || getClass() != o.getClass()) return false;
+                GrantReason that = (GrantReason) o;
+                return override == that.override
+                        && Objects.equals(reason, that.reason)
+                        && Objects.equals(fixed, that.fixed);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(reason, override, fixed);
+            }
+        }
+
+        // packageName -> permission -> [reason]
+        public ArrayMap<String, ArrayMap<String, ArraySet<GrantReason>>> mGrantReasons =
+                new ArrayMap<>();
+        public ArrayMap<String, Boolean> grantedPermissions = new ArrayMap<>();
+
+        public void log() {
+            for (String packageName : mGrantReasons.keySet()) {
+                Log.i(LOG_TAG, "  packageName: " + packageName + " {");
+
+                for (Map.Entry<String, Object> property :
+                        getPackageProperties(packageName).entrySet()) {
+                    Log.i(LOG_TAG, "    " + property.getKey() + ": " + property.getValue());
+                }
+
+                // Resort permission -> reason into reason -> permission
+                ArrayMap<String, ArraySet<GrantReason>> permissionsToReasons =
+                        mGrantReasons.get(packageName);
+                ArrayMap<GrantReason, List<String>> reasonsToPermissions = new ArrayMap<>();
+                for (String permission : permissionsToReasons.keySet()) {
+                    for (GrantReason reason : permissionsToReasons.get(permission)) {
+                        if (!reasonsToPermissions.containsKey(reason)) {
+                            reasonsToPermissions.put(reason, new ArrayList<>());
+                        }
+
+                        reasonsToPermissions.get(reason).add(permission);
+                    }
+                }
+
+                for (Map.Entry<GrantReason, List<String>> reasonEntry
+                        : reasonsToPermissions.entrySet()) {
+                    GrantReason reason = reasonEntry.getKey();
+                    Log.i(LOG_TAG, "    reason: " + reason.reason + " {");
+                    Log.i(LOG_TAG, "      override: " + reason.override);
+                    Log.i(LOG_TAG, "      fixed: " + reason.fixed);
+
+                    Log.i(LOG_TAG, "      permissions: [");
+                    for (String permission : reasonEntry.getValue()) {
+                        Log.i(LOG_TAG, "        " + permission + ",");
+                    }
+                    Log.i(LOG_TAG, "      ]");
+                    Log.i(LOG_TAG, "    }");
+
+                    // Do not overwhelm log
+                    try {
+                        Thread.sleep(50);
+                    } catch (InterruptedException e) {
+                        // ignored
+                    }
+                }
+
+                Log.i(LOG_TAG, "  }");
+            }
+        }
+
+        public void addGrantedPermission(String packageName, String reason, String permission,
+                Boolean fixed) {
+            Context context = getInstrumentation().getTargetContext();
+
+            // Add permissions split off from the permission to granted
+            try {
+                PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+                int targetSdk = info.applicationInfo.targetSdkVersion;
+
+                for (String extendedPerm : extendBySplitPermissions(permission, targetSdk)) {
+                    mergeGrantedPermission(packageName, extendedPerm.equals(permission) ? reason
+                                    : reason + " (split from " + permission + ")", extendedPerm,
+                            fixed, false);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // ignore
+            }
+        }
+
+        public void overrideGrantedPermission(String packageName, String reason, String permission,
+                Boolean fixed) {
+            mergeGrantedPermission(packageName, reason, permission, fixed, true);
+        }
+
+        public void mergeGrantedPermission(String packageName, String reason, String permission,
+                Boolean fixed, boolean override) {
+            if (!mGrantReasons.containsKey(packageName)) {
+                mGrantReasons.put(packageName, new ArrayMap<>());
+            }
+
+            if (!mGrantReasons.get(packageName).containsKey(permission)) {
+                mGrantReasons.get(packageName).put(permission, new ArraySet<>());
+            }
+
+            mGrantReasons.get(packageName).get(permission).add(new GrantReason(reason, override,
+                    fixed));
+
+            Boolean oldFixed = grantedPermissions.get(permission);
+            if (oldFixed == null) {
+                grantedPermissions.put(permission, fixed);
+            } else {
+                if (override) {
+                    if (oldFixed == Boolean.FALSE && fixed == Boolean.TRUE) {
+                        Log.w(LOG_TAG, "override already granted permission " + permission + "("
+                                + fixed + ") for " + packageName);
+                        grantedPermissions.put(permission, fixed);
+                    }
+                } else {
+                    if (oldFixed == Boolean.TRUE && fixed == Boolean.FALSE) {
+                        Log.w(LOG_TAG, "add already granted permission " + permission + "("
+                                + fixed + ") to " + packageName);
+                        grantedPermissions.put(permission, fixed);
+                    }
+                }
+            }
+        }
+    }
+
+    public static class DefaultPermissionGrantException {
+
+        public static final String UNSET_PLACEHOLDER = "(UNSET)";
+        public String company;
+        public String metadata;
+        public String pkg;
+        public String sha256;
+        public boolean hasBrand; // in rare cases, brand will be specified instead of SHA256 hash
+        public Map<String, Boolean> permissions = new HashMap<>();
+
+        public boolean hasNonBrandSha256() {
+            return sha256 != null && !hasBrand;
+        }
+
+        public DefaultPermissionGrantException(String pkg, String sha256,
+                Map<String, Boolean> permissions) {
+            this(UNSET_PLACEHOLDER, UNSET_PLACEHOLDER, pkg, sha256, permissions);
+        }
+
+        public DefaultPermissionGrantException(String company, String metadata, String pkg,
+                String sha256,
+                Map<String, Boolean> permissions) {
+            this.company = company;
+            this.metadata = metadata;
+            this.pkg = pkg;
+            this.sha256 = sha256;
+            if (!sha256.contains(":")) {
+                hasBrand = true; // rough approximation of brand vs. SHA256 hash
+            }
+            this.permissions = permissions;
+        }
+    }
+
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BatteryUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BatteryUtils.java
index 955321a..90758ef 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BatteryUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BatteryUtils.java
@@ -44,6 +44,12 @@
         return InstrumentationRegistry.getContext().getSystemService(PowerManager.class);
     }
 
+    public static boolean hasBattery() {
+        final Intent batteryInfo = InstrumentationRegistry.getContext()
+                .registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+        return batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
+    }
+
     /** Make the target device think it's off charger. */
     public static void runDumpsysBatteryUnplug() {
         SystemUtil.runShellCommandForNoOutput("cmd battery unplug");
@@ -91,28 +97,11 @@
             SystemUtil.runShellCommandForNoOutput("cmd power set-mode 1");
             putGlobalSetting(Global.LOW_POWER_MODE, "1");
             waitUntil("Battery saver still off", () -> getPowerManager().isPowerSaveMode());
-            waitUntil("Location mode still " + getPowerManager().getLocationPowerSaveMode(),
-                    () -> (PowerManager.LOCATION_MODE_NO_CHANGE
-                            != getPowerManager().getLocationPowerSaveMode()));
-
-            Thread.sleep(500);
-            waitUntil("Force all apps standby still off",
-                    () -> SystemUtil.runShellCommand("dumpsys alarm")
-                            .contains(" Force all apps standby: true\n"));
-
         } else {
             SystemUtil.runShellCommandForNoOutput("cmd power set-mode 0");
             putGlobalSetting(Global.LOW_POWER_MODE, "0");
             putGlobalSetting(Global.LOW_POWER_MODE_STICKY, "0");
             waitUntil("Battery saver still on", () -> !getPowerManager().isPowerSaveMode());
-            waitUntil("Location mode still " + getPowerManager().getLocationPowerSaveMode(),
-                    () -> (PowerManager.LOCATION_MODE_NO_CHANGE
-                            == getPowerManager().getLocationPowerSaveMode()));
-
-            Thread.sleep(500);
-            waitUntil("Force all apps standby still on",
-                    () -> SystemUtil.runShellCommand("dumpsys alarm")
-                            .contains(" Force all apps standby: false\n"));
         }
 
         AmUtils.waitForBroadcastIdle();
@@ -137,10 +126,8 @@
 
     /** @return true if the device supports battery saver. */
     public static boolean isBatterySaverSupported() {
-        final Intent batteryInfo = InstrumentationRegistry.getContext().registerReceiver(
-                null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
-        if (!batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true)) {
-            // Devices without battery does not support battery saver.
+        if (!hasBattery()) {
+            // Devices without a battery don't support battery saver.
             return false;
         }
 
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
index 6e71e10..f4f7a33 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
@@ -62,10 +62,10 @@
     }
 
     protected void executeBusinessLogic() {
-        executeBusinessLogifForTest(mTestCase.getMethodName());
+        executeBusinessLogicForTest(mTestCase.getMethodName());
     }
 
-    protected void executeBusinessLogifForTest(String methodName) {
+    protected void executeBusinessLogicForTest(String methodName) {
         assertTrue(String.format("Test \"%s\" is unable to execute as it depends on the missing "
                 + "remote configuration.", methodName), mCanReadBusinessLogic);
         if (methodName.contains(PARAM_START)) {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/OWNERS b/common/device-side/util-axt/src/com/android/compatibility/common/util/OWNERS
new file mode 100644
index 0000000..b06092c
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/OWNERS
@@ -0,0 +1 @@
+per-file BaseDefaultPermissionGrantPolicyTest.java = eugenesusla@google.com, moltmann@google.com, svetoslavganov@google.com
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ScreenUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ScreenUtils.java
new file mode 100644
index 0000000..86b16cb
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/ScreenUtils.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+public class ScreenUtils {
+
+    public static void setScreenOn(boolean on) throws Exception {
+        BatteryUtils.turnOnScreen(on);
+
+        // there is no way to listen for changes except broadcasts, and no way to guarantee
+        // broadcast reception except waiting and crossing fingers. 2s should be enough in the idle
+        // case, but may not be enough if the phone isn't idle
+        Thread.sleep(2000);
+    }
+
+    /**
+     * Try-with-resources class that resets the screen state on close to whatever the screen state
+     * was on acquire.
+     */
+    public static class ScreenResetter implements AutoCloseable {
+
+        private final boolean mScreenInteractive;
+
+        public ScreenResetter() {
+            mScreenInteractive = BatteryUtils.getPowerManager().isInteractive();
+        }
+
+        @Override
+        public void close() throws Exception {
+            BatteryUtils.turnOnScreen(mScreenInteractive);
+        }
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java
index 03d4a50..58447e3 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java
@@ -205,4 +205,26 @@
         return SystemUtil.runShellCommand(
                 String.format("settings --user %d get secure %s", userId, key)).trim();
     }
+
+    public static class SettingResetter implements AutoCloseable {
+        private final String mNamespace;
+        private final String mKey;
+        private final String mOldValue;
+
+        public SettingResetter(String namespace, String key, String value) {
+            mNamespace = namespace;
+            mKey = key;
+            mOldValue = get(namespace, key);
+            set(namespace, key, value);
+        }
+
+        @Override
+        public void close() {
+            if (mOldValue != null) {
+                set(mNamespace, mKey, mOldValue);
+            } else {
+                delete(mNamespace, mKey);
+            }
+        }
+    }
 }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
index a050094..8dc2b9b 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
@@ -35,6 +35,7 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Predicate;
 
@@ -99,15 +100,19 @@
      */
     static byte[] runShellCommandByteOutput(UiAutomation automation, String cmd)
             throws IOException {
+        checkCommandBeforeRunning(cmd);
+        ParcelFileDescriptor pfd = automation.executeShellCommand(cmd);
+        try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+            return FileUtils.readInputStreamFully(fis);
+        }
+    }
+
+    private static void checkCommandBeforeRunning(String cmd) {
         Log.v(TAG, "Running command: " + cmd);
         if (cmd.startsWith("pm grant ") || cmd.startsWith("pm revoke ")) {
             throw new UnsupportedOperationException("Use UiAutomation.grantRuntimePermission() "
                     + "or revokeRuntimePermission() directly, which are more robust.");
         }
-        ParcelFileDescriptor pfd = automation.executeShellCommand(cmd);
-        try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
-            return FileUtils.readInputStreamFully(fis);
-        }
     }
 
     /**
@@ -123,6 +128,48 @@
     }
 
     /**
+     * Like {@link #runShellCommand(String)} but throws if anything was printed to stderr.
+     */
+    public static String runShellCommandOrThrow(String cmd) {
+        UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            checkCommandBeforeRunning(cmd);
+
+            ParcelFileDescriptor[] fds = automation.executeShellCommandRwe(cmd);
+            ParcelFileDescriptor fdOut = fds[0];
+            ParcelFileDescriptor fdIn = fds[1];
+            ParcelFileDescriptor fdErr = fds[2];
+
+            if (fdIn != null) {
+                try {
+                    // not using stdin
+                    fdIn.close();
+                } catch (Exception e) {
+                    // Ignore
+                }
+            }
+
+            String out;
+            String err;
+            try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) {
+                out = new String(FileUtils.readInputStreamFully(fis));
+            }
+            try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdErr)) {
+                err = new String(FileUtils.readInputStreamFully(fis));
+            }
+            if (!err.isEmpty()) {
+                fail("Command failed:\n$ " + cmd +
+                        "\n\nstderr:\n" + err +
+                        "\n\nstdout:\n" + out);
+            }
+            return out;
+        } catch (IOException e) {
+            fail("Failed reading command output: " + e);
+            return "";
+        }
+    }
+
+    /**
      * Same as {@link #runShellCommand(String)}, with optionally
      * check the result using {@code resultChecker}.
      */
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
index 07133d6..c2e4224 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
@@ -36,7 +36,7 @@
     }
 
     public static UiObject2 waitFindObject(BySelector selector) throws UiObjectNotFoundException {
-        return waitFindObject(selector, 10_000);
+        return waitFindObject(selector, 20_000);
     }
 
     public static UiObject2 waitFindObject(BySelector selector, long timeoutMs)
@@ -51,21 +51,33 @@
 
     public static UiObject2 waitFindObjectOrNull(BySelector selector)
             throws UiObjectNotFoundException {
-        return waitFindObjectOrNull(selector, 10_000);
+        return waitFindObjectOrNull(selector, 20_000);
     }
 
     public static UiObject2 waitFindObjectOrNull(BySelector selector, long timeoutMs)
             throws UiObjectNotFoundException {
         UiObject2 view = null;
         long start = System.currentTimeMillis();
+
+        boolean isAtEnd = false;
+        boolean wasScrolledUpAlready = false;
         while (view == null && start + timeoutMs > System.currentTimeMillis()) {
-            view = getUiDevice().wait(Until.findObject(selector), timeoutMs / 10);
+            view = getUiDevice().wait(Until.findObject(selector), 1000);
 
             if (view == null) {
                 UiScrollable scrollable = new UiScrollable(new UiSelector().scrollable(true));
                 scrollable.setSwipeDeadZonePercentage(0.25);
                 if (scrollable.exists()) {
-                    scrollable.scrollForward();
+                    if (isAtEnd) {
+                        if (wasScrolledUpAlready) {
+                            return null;
+                        }
+                        scrollable.scrollToBeginning(Integer.MAX_VALUE);
+                        isAtEnd = false;
+                        wasScrolledUpAlready = true;
+                    } else {
+                        isAtEnd = !scrollable.scrollForward();
+                    }
                 }
             }
         }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
new file mode 100644
index 0000000..2541a4b
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util.enterprise;
+
+import static android.app.UiAutomation.FLAG_DONT_USE_ACCESSIBILITY;
+
+import static org.junit.Assume.assumeTrue;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+
+/**
+ * JUnit Rule which allows configuration of device state
+ */
+public final class DeviceState extends TestWatcher {
+
+    public enum UserType {
+        CURRENT_USER,
+        PRIMARY_USER,
+        SECONDARY_USER,
+        WORK_PROFILE
+    }
+
+    private static final String LOG_TAG = "DeviceState";
+
+    private static final Instrumentation sInstrumentation =
+            InstrumentationRegistry.getInstrumentation();
+
+    /**
+     * Copied from {@link android.content.pm.UserInfo}.
+     */
+    private static final int FLAG_PRIMARY = 0x00000001;
+
+    /**
+     * Copied from {@link android.content.pm.UserInfo}.
+     */
+    private static final int FLAG_MANAGED_PROFILE = 0x00000020;
+
+    /**
+     * Copied from {@link android.content.pm.UserInfo}.
+     */
+    private static final int FLAG_FULL = 0x00000400;
+
+    private List<Integer> createdUserIds = new ArrayList<>();
+
+    private UiAutomation mUiAutomation;
+    private final int MAX_UI_AUTOMATION_RETRIES = 5;
+
+    @Nullable
+    public UserHandle getWorkProfile() {
+        return getWorkProfile(/* forUser= */ UserType.CURRENT_USER);
+    }
+
+    @Nullable
+    public UserHandle getWorkProfile(UserType forUser) {
+        assumeTrue("Due to API limitations, tests cannot manage work profiles for users other " +
+                        "than the current one", forUser == UserType.CURRENT_USER);
+
+        UserManager userManager = sInstrumentation.getContext().getSystemService(UserManager.class);
+
+        for (UserHandle userHandle : userManager.getUserProfiles()) {
+            if ((getFlagsForUserID(userHandle.getIdentifier()) & FLAG_MANAGED_PROFILE) != 0) {
+                return userHandle;
+            }
+        }
+
+        return null;
+    }
+
+    public boolean isRunningOnWorkProfile() {
+        return getWorkProfile() != null
+                && getWorkProfile().getIdentifier() == android.os.UserHandle.myUserId();
+    }
+
+    private Integer getFlagsForUserID(int userId) {
+        ArrayList<String[]> users = tokenizeListUsers();
+        for (String[] user : users) {
+            int foundUserId = Integer.parseInt(user[1]);
+            if (userId == foundUserId) {
+                return Integer.parseInt(user[3], 16);
+            }
+        }
+        return null;
+    }
+
+    public boolean isRunningOnPrimaryUser() {
+        return android.os.UserHandle.myUserId() == getPrimaryUserId();
+    }
+
+    public boolean isRunningOnSecondaryUser() {
+        return UserHandle.myUserId() != getPrimaryUserId()
+                && (getFlagsForUserID(android.os.UserHandle.myUserId() & FLAG_FULL) != 0);
+    }
+
+    /**
+     * Get the first human user on the device.
+     *
+     * <p>Returns {@code null} if there is none present.
+     */
+    @Nullable
+    public UserHandle getPrimaryUser() {
+        Integer primaryUserId = getPrimaryUserId();
+        if (primaryUserId == null) {
+            return null;
+        }
+        return UserHandle.of(primaryUserId);
+    }
+
+    /**
+     * Get the first human user on the device other than the primary user.
+     *
+     * <p>Returns {@code null} if there is none present.
+     */
+    @Nullable
+    public UserHandle getSecondaryUser() {
+        Integer secondaryUserId = getSecondaryUserId();
+        if (secondaryUserId == null) {
+            return null;
+        }
+        return UserHandle.of(secondaryUserId);
+    }
+
+    /**
+     * Get the user ID of the first human user on the device.
+     *
+     * <p>Returns {@code null} if there is none present.
+     */
+    @Nullable
+    private Integer getPrimaryUserId() {
+        // This would be cleaner if there was a test api which could find this information
+        ArrayList<String[]> users = tokenizeListUsers();
+        for (String[] user : users) {
+            int flag = Integer.parseInt(user[3], 16);
+            if ((flag & FLAG_PRIMARY) != 0) {
+                return Integer.parseInt(user[1]);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the user ID of a human user on the device other than the primary user.
+     *
+     * <p>Returns {@code null} if there is none present.
+     */
+    @Nullable
+    private Integer getSecondaryUserId() {
+        // This would be cleaner if there was a test api which could find this information
+        ArrayList<String[]> users = tokenizeListUsers();
+        for (String[] user : users) {
+            int flag = Integer.parseInt(user[3], 16);
+            if (((flag & FLAG_PRIMARY) == 0) && ((flag & FLAG_FULL) != 0)) {
+                return Integer.parseInt(user[1]);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Tokenizes the output of 'pm list users'.
+     * The returned tokens for each user have the form: {"\tUserInfo", Integer.toString(id), name,
+     * Integer.toHexString(flag), "[running]"}; (the last one being optional)
+     * @return a list of arrays of strings, each element of the list representing the tokens
+     * for a user, or {@code null} if there was an error while tokenizing the adb command output.
+     */
+    private ArrayList<String[]> tokenizeListUsers() {
+        String command = "pm list users";
+        String commandOutput = runCommandWithOutput(command);
+        // Extract the id of all existing users.
+        String[] lines = commandOutput.split("\\r?\\n");
+        if (!lines[0].equals("Users:")) {
+            throw new RuntimeException(
+                    String.format("'%s' in not a valid output for 'pm list users'", commandOutput));
+        }
+        ArrayList<String[]> users = new ArrayList<String[]>(lines.length - 1);
+        for (int i = 1; i < lines.length; i++) {
+            // Individual user is printed out like this:
+            // \tUserInfo{$id$:$name$:$Integer.toHexString(flags)$} [running]
+            String[] tokens = lines[i].split("\\{|\\}|:");
+            if (tokens.length != 4 && tokens.length != 5) {
+                throw new RuntimeException(
+                        String.format(
+                                "device output: '%s' \nline: '%s' was not in the expected "
+                                        + "format for user info.",
+                                commandOutput, lines[i]));
+            }
+            users.add(tokens);
+        }
+        return users;
+    }
+
+    public void ensureHasWorkProfile(boolean installTestApp, UserType forUser) {
+        requireFeature("android.software.managed_users");
+        assumeTrue("Due to API limitations, tests cannot manage work profiles for users other " +
+                "than the current one", forUser == UserType.CURRENT_USER);
+
+        if (getWorkProfile() == null) {
+            createWorkProfile(resolveUserTypeToUserId(forUser));
+        }
+        if (installTestApp) {
+            installInProfile(getWorkProfile().getIdentifier(),
+                    sInstrumentation.getContext().getPackageName());
+        } else {
+            uninstallFromProfile(getWorkProfile().getIdentifier(),
+                    sInstrumentation.getContext().getPackageName());
+        }
+    }
+
+    public void ensureHasSecondaryUser(boolean installTestApp) {
+        // TODO: What is the requirement?
+        if (getSecondaryUser() == null) {
+            createSecondaryUser();
+        }
+        if (installTestApp) {
+            installInProfile(getSecondaryUserId(), sInstrumentation.getContext().getPackageName());
+        } else {
+            uninstallFromProfile(getSecondaryUserId(),
+                    sInstrumentation.getContext().getPackageName());
+        }
+    }
+
+    public void requireCanSupportAdditionalUser() {
+        int maxUsers = getMaxNumberOfUsersSupported();
+        int currentUsers = tokenizeListUsers().size();
+
+        assumeTrue("The device does not have space for an additional user (" + currentUsers +
+                " current users, " + maxUsers + " max users)", currentUsers + 1 <= maxUsers);
+    }
+
+    private int resolveUserTypeToUserId(UserType userType) {
+        switch (userType) {
+            case CURRENT_USER:
+                return android.os.UserHandle.myUserId();
+            case PRIMARY_USER:
+                return getPrimaryUserId();
+            case SECONDARY_USER:
+                return getSecondaryUserId();
+            case WORK_PROFILE:
+                return getWorkProfile().getIdentifier();
+            default:
+                throw new IllegalArgumentException("Unknown user type " + userType);
+        }
+    }
+
+    void teardown() {
+        for (Integer userId : createdUserIds) {
+            runCommandWithOutput("pm remove-user " + userId);
+        }
+
+        createdUserIds.clear();
+    }
+
+    private void createWorkProfile(int parentUserId) {
+        final String createUserOutput =
+                runCommandWithOutput(
+                        "pm create-user --profileOf " + parentUserId + " --managed work");
+        final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
+        runCommandWithOutput("am start-user -w " + profileId);
+        createdUserIds.add(profileId);
+    }
+
+    private void createSecondaryUser() {
+        requireCanSupportAdditionalUser();
+        final String createUserOutput =
+                runCommandWithOutput("pm create-user secondary");
+        final int userId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
+        runCommandWithOutput("am start-user -w " + userId);
+        createdUserIds.add(userId);
+    }
+
+    private void installInProfile(int profileId, String packageName) {
+        runCommandWithOutput("pm install-existing --user " + profileId + " " + packageName);
+    }
+
+    private void uninstallFromProfile(int profileId, String packageName) {
+        runCommandWithOutput("pm uninstall --user " + profileId + " " + packageName);
+    }
+
+    private String runCommandWithOutput(String command) {
+        ParcelFileDescriptor p = runCommand(command);
+
+        InputStream inputStream = new FileInputStream(p.getFileDescriptor());
+
+        try (Scanner scanner = new Scanner(inputStream, UTF_8.name())) {
+            return scanner.useDelimiter("\\A").next();
+        } catch (NoSuchElementException e) {
+            return "";
+        }
+    }
+
+    private ParcelFileDescriptor runCommand(String command) {
+        return getAutomation()
+                .executeShellCommand(command);
+    }
+
+    private UiAutomation getAutomation() {
+        if (mUiAutomation != null) {
+            return mUiAutomation;
+        }
+
+        int retries = MAX_UI_AUTOMATION_RETRIES;
+        mUiAutomation = sInstrumentation.getUiAutomation(FLAG_DONT_USE_ACCESSIBILITY);
+        while (mUiAutomation == null && retries > 0) {
+            Log.e(LOG_TAG, "Failed to get UiAutomation");
+            retries--;
+            mUiAutomation = sInstrumentation.getUiAutomation(FLAG_DONT_USE_ACCESSIBILITY);
+        }
+
+        if (mUiAutomation == null) {
+            throw new AssertionError("Could not get UiAutomation");
+        }
+
+        return mUiAutomation;
+    }
+
+    private void requireFeature(String feature) {
+        assumeTrue("Device must have feature " + feature,
+                sInstrumentation.getContext().getPackageManager().hasSystemFeature(feature));
+    }
+
+    private int getMaxNumberOfUsersSupported() {
+        String command = "pm get-max-users";
+        String commandOutput = runCommandWithOutput(command);
+        try {
+            return Integer.parseInt(commandOutput.substring(commandOutput.lastIndexOf(" ")).trim());
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException("Invalid command output", e);
+        }
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
new file mode 100644
index 0000000..b37176e
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
@@ -0,0 +1,7 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
+alexkershaw@google.com
+eranm@google.com
+rubinxu@google.com
+sandness@google.com
+pgrafov@google.com
+scottjonathan@google.com
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/Preconditions.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/Preconditions.java
new file mode 100644
index 0000000..57efce8
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/Preconditions.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util.enterprise;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.os.Bundle;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.compatibility.common.util.enterprise.annotations.EnsureHasSecondaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.EnsureHasWorkProfile;
+import com.android.compatibility.common.util.enterprise.annotations.RequireFeatures;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+
+/**
+ * A Junit rule which enforces preconditions in annotations from the
+ * {@code com.android.comaptibility.common.util.enterprise.annotations} package.
+ *
+ * {@code assumeTrue} will be used, so tests which do not meet preconditions will be skipped.
+ */
+public final class Preconditions implements TestRule {
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private final DeviceState mDeviceState;
+    private static final String SKIP_TEST_TEARDOWN_KEY = "skip-test-teardown";
+    private final boolean mSkipTestTeardown;
+
+    public Preconditions(DeviceState deviceState) {
+        mDeviceState = deviceState;
+        Bundle arguments = InstrumentationRegistry.getArguments();
+        mSkipTestTeardown = Boolean.parseBoolean(arguments.getString(SKIP_TEST_TEARDOWN_KEY, "false"));
+    }
+
+    @Override public Statement apply(final Statement base,
+            final Description description) {
+        return new Statement() {
+            @Override public void evaluate() throws Throwable {
+                if (description.getAnnotation(RequireRunOnPrimaryUser.class) != null) {
+                    assumeTrue("@RequireRunOnPrimaryUser tests only run on primary user",
+                            mDeviceState.isRunningOnPrimaryUser());
+                }
+                if (description.getAnnotation(RequireRunOnWorkProfile.class) != null) {
+                    assumeTrue("@RequireRunOnWorkProfile tests only run on work profile",
+                            mDeviceState.isRunningOnWorkProfile());
+                }
+                if (description.getAnnotation(RequireRunOnSecondaryUser.class) != null) {
+                    assumeTrue("@RequireRunOnSecondaryUser tests only run on secondary user",
+                            mDeviceState.isRunningOnSecondaryUser());
+                }
+                EnsureHasWorkProfile ensureHasWorkAnnotation =
+                        description.getAnnotation(EnsureHasWorkProfile.class);
+                if (ensureHasWorkAnnotation != null) {
+                    mDeviceState.ensureHasWorkProfile(
+                            /* installTestApp= */ ensureHasWorkAnnotation.installTestApp(),
+                            /* forUser= */ ensureHasWorkAnnotation.forUser()
+                    );
+                }
+                EnsureHasSecondaryUser ensureHasSecondaryUserAnnotation =
+                        description.getAnnotation(EnsureHasSecondaryUser.class);
+                if (ensureHasSecondaryUserAnnotation != null) {
+                    mDeviceState.ensureHasSecondaryUser(
+                            /* installTestApp= */ ensureHasSecondaryUserAnnotation.installTestApp()
+                    );
+                }
+                RequireFeatures requireFeaturesAnnotation =
+                        description.getAnnotation(RequireFeatures.class);
+                if (requireFeaturesAnnotation != null) {
+                    for (String feature: requireFeaturesAnnotation.featureNames()) {
+                        requireFeature(feature);
+                    }
+                }
+
+                base.evaluate();
+
+                if (!mSkipTestTeardown) {
+                    mDeviceState.teardown();
+                }
+        }};
+    }
+
+    private void requireFeature(String feature) {
+        assumeTrue("Device must have feature " + feature,
+                mContext.getPackageManager().hasSystemFeature(feature));
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java
new file mode 100644
index 0000000..4f5e582
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util.enterprise.annotations;
+
+import com.android.compatibility.common.util.enterprise.Preconditions;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a device which has a secondary user that is not the
+ * current user.
+ *
+ * <p>Your test configuration may be configured so that this test is only runs on a device which
+ * has a secondary user that is not the current user. Otherwise, you can use {@link Preconditions}
+ * to ensure that the device enters the correct state for the method. If there is not already a
+ * secondary user on the device, and the device does not support creating additional users, then
+ * the test will be skipped.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasSecondaryUser {
+    /** Should the test app be installed in the secondary user. */
+    boolean installTestApp() default true;
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java
new file mode 100644
index 0000000..eddcd06
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util.enterprise.annotations;
+
+import static com.android.compatibility.common.util.enterprise.DeviceState.UserType.CURRENT_USER;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.compatibility.common.util.enterprise.Preconditions;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a user which has a work profile.
+ *
+ * <p>Use of this annotation implies {@code RequireFeatures("android.software.managed_users")}.
+ *
+ * <p>Your test configuration may be configured so that this test is only runs on a user which has
+ * a work profile. Otherwise, you can use {@link Preconditions} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasWorkProfile {
+    /** Which user type should the work profile be attached to. */
+    DeviceState.UserType forUser() default CURRENT_USER;
+
+    /** Should the test app be installed in the work profile. */
+    boolean installTestApp() default true;
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireFeatures.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireFeatures.java
new file mode 100644
index 0000000..61f76d8
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireFeatures.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util.enterprise.annotations;
+
+import com.android.compatibility.common.util.enterprise.Preconditions;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run only when the device has a given feature.
+ *
+ * <p>You can guarantee that these methods do not run on devices lacking the feature by
+ * using {@link Preconditions}.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireFeatures {
+    String[] featureNames();
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java
new file mode 100644
index 0000000..eb37581
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util.enterprise.annotations;
+
+import com.android.compatibility.common.util.enterprise.Preconditions;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on the primary user.
+ *
+ * <p>Your test configuration should be such that this test is only run on the primary user
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of the primary
+ * user by using {@link Preconditions}.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireRunOnPrimaryUser {
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java
new file mode 100644
index 0000000..dcabf6f
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util.enterprise.annotations;
+
+import com.android.compatibility.common.util.enterprise.Preconditions;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a secondary user.
+ *
+ * <p>Your test configuration should be such that this test is only run where a secondary user is
+ * created and the tests is being run on that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of a secondary user by
+ * using {@link Preconditions}.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireRunOnSecondaryUser {
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java
new file mode 100644
index 0000000..9b347b4
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util.enterprise.annotations;
+
+import com.android.compatibility.common.util.enterprise.Preconditions;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run within a work profile.
+ *
+ * <p>Your test configuration should be such that this test is only run where a work profile is
+ * created and the tests is being run within that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of a work
+ * profile by using {@link Preconditions}.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireRunOnWorkProfile {
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
index 571a1b8..5a6a5ef 100644
--- a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
+++ b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
@@ -118,7 +118,7 @@
                         new RetryableStatement<RetryableException>(3, sRetryableException, cleaner),
                         mDescription).evaluate());
 
-        assertThat(actualException).isSameAs(sRetryableException);
+        assertThat(actualException).isSameInstanceAs(sRetryableException);
         verify(cleaner, times(2)).run();
     }
 
@@ -143,7 +143,7 @@
         final RetryableException actualException = expectThrows(RetryableException.class,
                 () -> rule.apply(mMockStatement, mDescription).evaluate());
 
-        assertThat(actualException).isSameAs(exception);
+        assertThat(actualException).isSameInstanceAs(exception);
         verify(mMockStatement, times(1)).evaluate();
         verify(cleaner, never()).run();
     }
@@ -169,7 +169,7 @@
                 () -> rule.apply(new RetryableStatement<RetryableException>(2, sRetryableException),
                         mDescription).evaluate());
 
-        assertThat(actualException).isSameAs(sRetryableException);
+        assertThat(actualException).isSameInstanceAs(sRetryableException);
     }
 
     @Test
@@ -190,7 +190,7 @@
         final RetryableException actualException = expectThrows(RetryableException.class,
                 () -> rule.apply(mMockStatement, mDescription).evaluate());
 
-        assertThat(actualException).isSameAs(exception);
+        assertThat(actualException).isSameInstanceAs(exception);
         verify(mMockStatement, times(1)).evaluate();
     }
 
@@ -203,7 +203,7 @@
         final RuntimeException actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mMockStatement, mDescription).evaluate());
 
-        assertThat(actualException).isSameAs(exception);
+        assertThat(actualException).isSameInstanceAs(exception);
         verify(mMockStatement, times(1)).evaluate();
     }
 }
diff --git a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java
index a56d7b2..9c82a1e 100644
--- a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java
+++ b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java
@@ -75,7 +75,7 @@
         final SafeCleanerRule rule = new SafeCleanerRule();
         final Throwable actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
     }
 
     @Test
@@ -83,7 +83,7 @@
         final SafeCleanerRule rule = new SafeCleanerRule().setDumper(mDumper);
         final Throwable actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
         verify(mDumper).dump("Whatever", actualException);
     }
 
@@ -94,7 +94,7 @@
                 .add(mGoodGuyExtraExceptions1);
         final Throwable actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
         verify(mGoodGuyRunner1).run();
         verify(mGoodGuyExtraExceptions1).call();
     }
@@ -107,7 +107,7 @@
                 .add(mGoodGuyExtraExceptions1);
         final Throwable actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
         verify(mGoodGuyRunner1).run();
         verify(mGoodGuyExtraExceptions1).call();
         verify(mDumper).dump("Whatever", actualException);
@@ -122,7 +122,7 @@
                 .add(mGoodGuyExtraExceptions1);
         final Throwable actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
         verify(mGoodGuyRunner1).run();
         verify(mGoodGuyRunner2).run();
         verify(mGoodGuyExtraExceptions1).call();
@@ -140,7 +140,7 @@
                 .add(mGoodGuyExtraExceptions1);
         final Throwable actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
         verify(mGoodGuyRunner1).run();
         verify(mGoodGuyRunner2).run();
         verify(mGoodGuyExtraExceptions1).call();
@@ -156,7 +156,7 @@
                 .run(mGoodGuyRunner2);
         final Throwable actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
         verify(mGoodGuyRunner1).run();
         verify(mGoodGuyRunner2).run();
         verify(mGoodGuyExtraExceptions1).call();
@@ -173,7 +173,7 @@
                 .run(mGoodGuyRunner2);
         final Throwable actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
         verify(mGoodGuyRunner1).run();
         verify(mGoodGuyRunner2).run();
         verify(mGoodGuyExtraExceptions1).call();
@@ -189,7 +189,7 @@
                 .run(mGoodGuyRunner2);
         final Throwable actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
         verify(mGoodGuyRunner1).run();
         verify(mGoodGuyRunner2).run();
         verify(mGoodGuyExtraExceptions1).call();
diff --git a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/StateChangerRuleTest.java b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/StateChangerRuleTest.java
index 9b1851e..7559ddc 100644
--- a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/StateChangerRuleTest.java
+++ b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/StateChangerRuleTest.java
@@ -117,7 +117,7 @@
 
         final RuntimeException actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
 
         verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
         verify(mStateManager, times(1)).set("newValue");
@@ -134,7 +134,7 @@
 
         final RuntimeException actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
 
         verify(mStateManager, never()).set(anyString());
     }
@@ -148,7 +148,7 @@
 
         final RuntimeException actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
 
         verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
         verify(mStateManager, times(1)).set("newValue");
@@ -164,7 +164,7 @@
 
         final RuntimeException actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
 
         verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
         verify(mStateManager, times(1)).set("sameValue");
diff --git a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/StateKeeperRuleTest.java b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/StateKeeperRuleTest.java
index 4599aca..becf079 100644
--- a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/StateKeeperRuleTest.java
+++ b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/StateKeeperRuleTest.java
@@ -74,7 +74,7 @@
         final RuntimeException actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mStatement, mDescription).evaluate());
 
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
         verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
         verify(mStateManager, times(1)).set("before");
         verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
@@ -100,7 +100,7 @@
         final RuntimeException actualException = expectThrows(RuntimeException.class,
                 () -> rule.apply(mStatement, mDescription).evaluate());
 
-        assertThat(actualException).isSameAs(mRuntimeException);
+        assertThat(actualException).isSameInstanceAs(mRuntimeException);
 
         verify(mStateManager, never()).set(anyString());
     }
diff --git a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/TimeoutTest.java b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/TimeoutTest.java
index 8992d18..fdc4de6 100644
--- a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/TimeoutTest.java
+++ b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/TimeoutTest.java
@@ -105,7 +105,7 @@
         final Timeout timeout = new Timeout(mSleeper, NAME, 100, 2, 500);
         final Object result = new Object();
         when(mJob.call()).thenReturn(result);
-        assertThat(timeout.run(DESC, 1, mJob)).isSameAs(result);
+        assertThat(timeout.run(DESC, 1, mJob)).isSameInstanceAs(result);
         assertThat(mSleeper.totalSleepingTime).isEqualTo(0);
     }
 
@@ -114,7 +114,7 @@
         final Timeout timeout = new Timeout(mSleeper, NAME, 100, 2, 500);
         final Object result = new Object();
         when(mJob.call()).thenReturn((Object) null, result);
-        assertThat(timeout.run(DESC, 10, mJob)).isSameAs(result);
+        assertThat(timeout.run(DESC, 10, mJob)).isSameInstanceAs(result);
         assertThat(mSleeper.totalSleepingTime).isEqualTo(10);
     }
 
@@ -124,7 +124,7 @@
         final RetryableException e = expectThrows(RetryableException.class,
                 () -> timeout.run(DESC, 10, mJob));
         assertThat(e.getMessage()).contains(DESC);
-        assertThat(e.getTimeout()).isSameAs(timeout);
+        assertThat(e.getTimeout()).isSameInstanceAs(timeout);
         assertThat(mSleeper.totalSleepingTime).isEqualTo(100);
     }
 
diff --git a/common/host-side/util-axt/src/com/android/compatibility/common/util/WindowManagerUtil.java b/common/host-side/util-axt/src/com/android/compatibility/common/util/WindowManagerUtil.java
index 7f165ed..e03cb84 100644
--- a/common/host-side/util-axt/src/com/android/compatibility/common/util/WindowManagerUtil.java
+++ b/common/host-side/util-axt/src/com/android/compatibility/common/util/WindowManagerUtil.java
@@ -64,7 +64,8 @@
             @Nonnull String expectedTitle) throws Exception {
         List<WindowStateProto> windows = getWindows(device);
         for (WindowStateProto window : windows) {
-            if (expectedTitle.equals(window.getIdentifier().getTitle())) {
+            String title = window.getWindowContainer().getIdentifier().getTitle(); 
+            if (expectedTitle.equals(title)) {
                 return window;
             }
         }
diff --git a/hostsidetests/abioverride/TEST_MAPPING b/hostsidetests/abioverride/TEST_MAPPING
new file mode 100644
index 0000000..ae76c92
--- /dev/null
+++ b/hostsidetests/abioverride/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAbiOverrideHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/abioverride/app/AndroidManifest.xml b/hostsidetests/abioverride/app/AndroidManifest.xml
index 6135732..d7215df 100755
--- a/hostsidetests/abioverride/app/AndroidManifest.xml
+++ b/hostsidetests/abioverride/app/AndroidManifest.xml
@@ -16,15 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.abioverride.app">
+     package="android.abioverride.app">
 
-    <application android:use32bitAbi="true" android:multiArch="true">
-        <uses-library android:name="android.test.runner" />
+    <application android:use32bitAbi="true"
+         android:multiArch="true">
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name=".AbiOverrideActivity" >
+        <activity android:name=".AbiOverrideActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/angle/app/driverTest/AndroidManifest.xml b/hostsidetests/angle/app/driverTest/AndroidManifest.xml
index 865b836..73392df 100755
--- a/hostsidetests/angle/app/driverTest/AndroidManifest.xml
+++ b/hostsidetests/angle/app/driverTest/AndroidManifest.xml
@@ -16,24 +16,22 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.angleIntegrationTest.driverTest"
-    android:targetSandboxVersion="2">
+     package="com.android.angleIntegrationTest.driverTest"
+     android:targetSandboxVersion="2">
 
     <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity" >
+        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.angleIntegrationTest.driverTest" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.angleIntegrationTest.driverTest"/>
 
 </manifest>
-
-
diff --git a/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml b/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
index a91da6d..e88a8c3 100755
--- a/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
+++ b/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
@@ -16,22 +16,22 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.angleIntegrationTest.driverTestSecondary"
-    android:targetSandboxVersion="2">
+     package="com.android.angleIntegrationTest.driverTestSecondary"
+     android:targetSandboxVersion="2">
 
     <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity" >
+        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.angleIntegrationTest.driverTestSecondary" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.angleIntegrationTest.driverTestSecondary"/>
 
 </manifest>
diff --git a/hostsidetests/appbinding/app/app1/AndroidManifest.xml b/hostsidetests/appbinding/app/app1/AndroidManifest.xml
index 12bc545..615315f 100644
--- a/hostsidetests/appbinding/app/app1/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app1/AndroidManifest.xml
@@ -15,72 +15,75 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name=".MyService"
-            android:exported="true"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
+        <service android:name=".MyService"
+             android:exported="true"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app2/AndroidManifest.xml b/hostsidetests/appbinding/app/app2/AndroidManifest.xml
index 9541cd2..b0935a5 100644
--- a/hostsidetests/appbinding/app/app2/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app2/AndroidManifest.xml
@@ -15,72 +15,75 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name=".MyService2"
-            android:exported="false"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE" >
+        <service android:name=".MyService2"
+             android:exported="false"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app3/AndroidManifest.xml b/hostsidetests/appbinding/app/app3/AndroidManifest.xml
index ef89aba..e62c282 100644
--- a/hostsidetests/appbinding/app/app3/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app3/AndroidManifest.xml
@@ -15,71 +15,74 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name=".MyService"
-            android:exported="false"
-            android:process=":persistent">
+        <service android:name=".MyService"
+             android:exported="false"
+             android:process=":persistent">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app4/AndroidManifest.xml b/hostsidetests/appbinding/app/app4/AndroidManifest.xml
index 07bd5ed..b934439 100644
--- a/hostsidetests/appbinding/app/app4/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app4/AndroidManifest.xml
@@ -15,81 +15,83 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name=".MyService"
-            android:exported="true"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE" >
+        <service android:name=".MyService"
+             android:exported="true"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
-        <service
-            android:name=".MyService2"
-            android:exported="true"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE" >
+        <service android:name=".MyService2"
+             android:exported="true"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app5/AndroidManifest.xml b/hostsidetests/appbinding/app/app5/AndroidManifest.xml
index 68f83a6..8397140 100644
--- a/hostsidetests/appbinding/app/app5/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app5/AndroidManifest.xml
@@ -15,63 +15,67 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- No target services. -->
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app6/AndroidManifest.xml b/hostsidetests/appbinding/app/app6/AndroidManifest.xml
index 68fea2c..4a256ea 100644
--- a/hostsidetests/appbinding/app/app6/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app6/AndroidManifest.xml
@@ -15,71 +15,74 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service found, but doesn't have :process. -->
-        <service
-            android:name=".MyService2"
-            android:exported="false"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE" >
+        <service android:name=".MyService2"
+             android:exported="false"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app7/AndroidManifest.xml b/hostsidetests/appbinding/app/app7/AndroidManifest.xml
index e0a3e9d..3e173e2 100644
--- a/hostsidetests/appbinding/app/app7/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app7/AndroidManifest.xml
@@ -15,73 +15,76 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name=".MyService"
-            android:exported="true"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE"
-            android:enabled="false">
+        <service android:name=".MyService"
+             android:exported="true"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE"
+             android:enabled="false">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/appb/AndroidManifest.xml b/hostsidetests/appbinding/app/appb/AndroidManifest.xml
index fac204e..4b6499e 100644
--- a/hostsidetests/appbinding/app/appb/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/appb/AndroidManifest.xml
@@ -15,72 +15,75 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app.b" >
+     package="com.android.cts.appbinding.app.b">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name="com.android.cts.appbinding.app.MyService"
-            android:exported="true"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE" >
+        <service android:name="com.android.cts.appbinding.app.MyService"
+             android:exported="true"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name="com.android.cts.appbinding.app.MySendToActivity">
+        <activity android:name="com.android.cts.appbinding.app.MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name="com.android.cts.appbinding.app.sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="com.android.cts.appbinding.app.sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name="com.android.cts.appbinding.app.sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appcompat/compatchanges/selinuxapp/Android.bp b/hostsidetests/appcompat/compatchanges/selinuxapp/Android.bp
new file mode 100644
index 0000000..472d554
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/selinuxapp/Android.bp
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+java_library_static {
+    name: "selinux_app_empty",
+    sdk_version: "current",
+    srcs: ["src/**/*.java"],
+}
+
+android_test_helper_app {
+    name: "CtsSelinuxQCompatApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    static_libs: ["selinux_app_empty"],
+    manifest: "AndroidManifest_Q.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSelinuxRCompatApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    static_libs: ["selinux_app_empty"],
+    manifest: "AndroidManifest_R.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_Q.xml b/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_Q.xml
new file mode 100644
index 0000000..5a6c1d3
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_Q.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.appcompat.selinux_app">
+    <uses-sdk android:targetSdkVersion="29"/>
+    <application
+        android:debuggable="true">
+         <activity android:name=".Empty"
+         android:exported="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.appcompat.selinux_app" />
+
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_R.xml b/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_R.xml
new file mode 100644
index 0000000..0fecd4f
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_R.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.appcompat.selinux_app">
+    <uses-sdk android:targetSdkVersion="30"/>
+    <application
+        android:debuggable="true">
+         <activity android:name=".Empty"
+         android:exported="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.appcompat.selinux_app" />
+
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/selinuxapp/src/com/android/cts/appcompat/selinux_app/Empty.java b/hostsidetests/appcompat/compatchanges/selinuxapp/src/com/android/cts/appcompat/selinux_app/Empty.java
new file mode 100644
index 0000000..a350bc0
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/selinuxapp/src/com/android/cts/appcompat/selinux_app/Empty.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.appcompat.selinux_app;
+
+import android.app.Activity;
+
+public class Empty extends Activity {
+
+}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
new file mode 100644
index 0000000..1006c47
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appcompat;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Tests for the {@link android.app.compat.CompatChanges} SystemApi.
+ */
+
+public class CompatChangesSelinuxTest extends CompatChangeGatingTestCase {
+
+    protected static final String Q_TEST_APK = "CtsSelinuxQCompatApp.apk";
+    protected static final String R_TEST_APK = "CtsSelinuxRCompatApp.apk";
+
+    protected static final String TEST_PKG = "com.android.cts.appcompat.selinux_app";
+
+    private static final long SELINUX_LATEST_CHANGES = 143539591L;
+    private static final long SELINUX_R_CHANGES = 168782947L;
+
+    private static final Pattern PS_ENTRY_PATTERN = Pattern.compile("^(?<label>\\S+)\\s+(?<name>\\S+)");
+
+
+    public void testTargetSdkQAppIsInQDomainByDefault() throws Exception {
+        installPackage(Q_TEST_APK, false);
+        try {
+            startApp();
+            Map<String, String> packageToDomain = getPackageToDomain();
+
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app_29");
+        } finally {
+            uninstallPackage(TEST_PKG, true);
+        }
+    }
+
+    public void testTargetSdkQAppIsInLatestDomainWithLatestOptin() throws Exception {
+        final Set<Long> enabledChanges = ImmutableSet.of(SELINUX_LATEST_CHANGES);
+        final Set<Long> disabledChanges = ImmutableSet.of();
+        final long configId = getClass().getCanonicalName().hashCode();
+
+        installPackage(Q_TEST_APK, false);
+        Thread.currentThread().sleep(100);
+        setCompatConfig(enabledChanges, disabledChanges, TEST_PKG);
+
+        try {
+            startApp();
+            Map<String, String> packageToDomain = getPackageToDomain();
+
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app");
+
+        } finally {
+            resetCompatConfig(TEST_PKG, enabledChanges, disabledChanges);
+            uninstallPackage(TEST_PKG, true);
+        }
+    }
+
+    public void testTargetSdkQAppIsInRDomainWithROptin() throws Exception {
+        final Set<Long> enabledChanges = ImmutableSet.of(SELINUX_R_CHANGES);
+        final Set<Long> disabledChanges = ImmutableSet.of();
+
+        installPackage(Q_TEST_APK, false);
+        Thread.currentThread().sleep(100);
+        setCompatConfig(enabledChanges, disabledChanges, TEST_PKG);
+
+        try {
+            startApp();
+            Map<String, String> packageToDomain = getPackageToDomain();
+            // TODO(b/168782947): Update domain if/when an R specific one is created to
+            // differentiate from untrusted_app.
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app");
+
+        } finally {
+            resetCompatConfig(TEST_PKG, enabledChanges, disabledChanges);
+            uninstallPackage(TEST_PKG, true);
+        }
+    }
+
+    public void testTargetSdkRAppIsInRDomainByDefault() throws Exception {
+        installPackage(R_TEST_APK, false);
+        try {
+            startApp();
+            Map<String, String> packageToDomain = getPackageToDomain();
+
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app");
+        } finally {
+            uninstallPackage(TEST_PKG, true);
+        }
+    }
+
+    public void testTargetSdkRAppIsInLatestDomainWithLatestOptin() throws Exception {
+        final Set<Long> enabledChanges = ImmutableSet.of(SELINUX_LATEST_CHANGES);
+        final Set<Long> disabledChanges = ImmutableSet.of();
+        installPackage(R_TEST_APK, false);
+        Thread.currentThread().sleep(100);
+        setCompatConfig(enabledChanges, disabledChanges, TEST_PKG);
+
+        try {
+            startApp();
+            Map<String, String> packageToDomain = getPackageToDomain();
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app");
+        } finally {
+            resetCompatConfig(TEST_PKG, enabledChanges, disabledChanges);
+            uninstallPackage(TEST_PKG, true);
+        }
+    }
+
+    private Map<String, String> getPackageToDomain() throws Exception {
+        Map<String, String> packageToDomain = new HashMap<>();
+        String output = getDevice().executeShellCommand("ps -e -o LABEL,NAME");
+        String[] lines = output.split("\n");
+        for (int i = 1; i < lines.length; ++i) {
+            String line = lines[i];
+            Matcher matcher = PS_ENTRY_PATTERN.matcher(line);
+            if (!matcher.matches())
+                continue;
+            String label = matcher.group("label");
+            String domain = label.split(":")[2];
+            String packageName = matcher.group("name");
+            packageToDomain.put(packageName, domain);
+        }
+        return packageToDomain;
+    }
+
+    private void startApp() throws Exception {
+        runCommand("am start -n " + TEST_PKG + "/" + TEST_PKG + ".Empty");
+        Thread.currentThread().sleep(100);
+    }
+
+
+}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
index c5e72b9..8088411 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
@@ -208,7 +208,7 @@
      * The device may contain extra changes, but none may be removed.
      */
     public void testDeviceContainsExpectedConfig() throws Exception {
-        assertThat(getOnDeviceCompatConfig()).containsAllIn(getExpectedCompatConfig());
+        assertThat(getOnDeviceCompatConfig()).containsAtLeastElementsIn(getExpectedCompatConfig());
     }
 
 }
diff --git a/hostsidetests/appcompat/hiddenapi/Android.bp b/hostsidetests/appcompat/hiddenapi/Android.bp
new file mode 100644
index 0000000..9c292b4
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "CtsHostsideHiddenapiTests",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "host-libprotobuf-java-full",
+        "platformprotos",
+    ],
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appcompat/hiddenapi/AndroidTest.xml b/hostsidetests/appcompat/hiddenapi/AndroidTest.xml
new file mode 100644
index 0000000..6064214
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS hiddenapi host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsHostsideHiddenapiTests.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/appcompat/hiddenapi/app/Android.bp b/hostsidetests/appcompat/hiddenapi/app/Android.bp
new file mode 100644
index 0000000..34c966a
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/app/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsHiddenApiApp",
+    defaults: ["cts_defaults"],
+    min_sdk_version: "24",
+    srcs: [
+        "src/**/*.java",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appcompat/hiddenapi/app/AndroidManifest.xml b/hostsidetests/appcompat/hiddenapi/app/AndroidManifest.xml
new file mode 100644
index 0000000..9454b60
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/app/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.compat.hiddenapi.cts"
+     android:versionCode="10">
+
+    <application android:label="@string/app_name">
+        <activity android:name=".HiddenApiUsedActivity"
+             android:exported="true"/>
+
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.server.cts.device.statsd"
+         android:label="CTS tests of android.os.statsd stats collection">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/appcompat/hiddenapi/app/res/values/strings.xml b/hostsidetests/appcompat/hiddenapi/app/res/values/strings.xml
new file mode 100644
index 0000000..833f90e
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/app/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">CTS HiddenApi App</string>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/appcompat/hiddenapi/app/src/android/compat/hiddenapi/cts/HiddenApiUsedActivity.java b/hostsidetests/appcompat/hiddenapi/app/src/android/compat/hiddenapi/cts/HiddenApiUsedActivity.java
new file mode 100644
index 0000000..f4bd128
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/app/src/android/compat/hiddenapi/cts/HiddenApiUsedActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.compat.hiddenapi.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import java.lang.reflect.Field;
+
+
+public class HiddenApiUsedActivity extends Activity {
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        try {
+            Field field = Activity.class.getDeclaredField("mWindow");
+            field.setAccessible(true);
+            Object object = field.get(this);
+        } catch(NoSuchFieldException e) {
+        } catch(IllegalAccessException e) {
+        }
+        finish();
+    }
+
+}
\ No newline at end of file
diff --git a/hostsidetests/appcompat/hiddenapi/src/android/compat/hiddenapi/cts/HostsideStatsdAtomTests.java b/hostsidetests/appcompat/hiddenapi/src/android/compat/hiddenapi/cts/HostsideStatsdAtomTests.java
new file mode 100644
index 0000000..16df276
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/src/android/compat/hiddenapi/cts/HostsideStatsdAtomTests.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.compat.hiddenapi.cts;
+
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.HiddenApiUsed;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+
+public class HostsideStatsdAtomTests extends DeviceTestCase implements IBuildReceiver {
+    private static final String TEST_PKG = "android.compat.hiddenapi.cts";
+    private static final String TEST_APK = "CtsHiddenApiApp.apk";
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        // Test package installed by HostsideNetworkTestCase
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        // Test package uninstalled by HostsideNetworkTestCase
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testHiddenApiUsed() throws Exception {
+        String oldRate = getDevice().executeShellCommand(
+                "device_config get app_compat hidden_api_access_statslog_sampling_rate").trim();
+
+        getDevice().executeShellCommand(
+                "device_config put app_compat hidden_api_access_statslog_sampling_rate 65536");
+
+        DeviceUtils.installTestApp(getDevice(), TEST_APK, TEST_PKG, mCtsBuild);
+
+        try {
+            final int atomTag = Atom.HIDDEN_API_USED_FIELD_NUMBER;
+
+             // Upload the config.
+            final StatsdConfig.Builder config = ConfigUtils.createConfigBuilder(TEST_PKG);
+            ConfigUtils.addEventMetricForUidAtom(config,  Atom.HIDDEN_API_USED_FIELD_NUMBER,
+                    /*uidInAttributionChain=*/false, TEST_PKG);
+            ConfigUtils.uploadConfig(getDevice(), config);
+
+            // Trigger hidden api event.
+            runActivity(getDevice(), TEST_PKG, "HiddenApiUsedActivity",
+                    /*actionKey=*/null, /*actionValue=*/null);
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+            List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+            assertThat(data).hasSize(1);
+
+            HiddenApiUsed atom = data.get(0).getAtom().getHiddenApiUsed();
+
+            final int appUid = DeviceUtils.getAppUid(getDevice(), TEST_PKG);
+            assertThat(atom.getUid()).isEqualTo(appUid);
+            assertThat(atom.getAccessDenied()).isFalse();
+            assertThat(atom.getSignature())
+                .isEqualTo("Landroid/app/Activity;->mWindow:Landroid/view/Window;");
+        } finally {
+            if (!oldRate.equals("null")) {
+                getDevice().executeShellCommand(
+                        "device_config put app_compat hidden_api_access_statslog_sampling_rate "
+                        + oldRate);
+            } else {
+                getDevice().executeShellCommand(
+                        "device_config delete hidden_api_access_statslog_sampling_rate");
+            }
+            DeviceUtils.uninstallTestApp(getDevice(), TEST_PKG);
+        }
+    }
+        /**
+     * Runs an activity in a particular app.
+     */
+    public static void runActivity(ITestDevice device, String pkgName, String activity,
+            @Nullable String actionKey, @Nullable String actionValue) throws Exception {
+        runActivity(device, pkgName, activity, actionKey, actionValue,
+                AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    /**
+     * Runs an activity in a particular app for a certain period of time.
+     *
+     * @param pkgName name of package that contains the Activity
+     * @param activity name of the Activity class
+     * @param actionKey key of extra data that is passed to the Activity via an Intent
+     * @param actionValue value of extra data that is passed to the Activity via an Intent
+     * @param waitTimeMs duration that the activity runs for
+     */
+    public static void runActivity(ITestDevice device, String pkgName, String activity,
+            @Nullable String actionKey, @Nullable String actionValue, long waitTimeMs)
+            throws Exception {
+        try (AutoCloseable a = withActivity(device, pkgName, activity, actionKey, actionValue)) {
+            Thread.sleep(waitTimeMs);
+        }
+    }
+
+    /**
+     * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
+     * when closed.
+     *
+     * <p>Example usage:
+     * <pre>
+     *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
+     *         doStuff();
+     *     }
+     * </pre>
+     */
+    public static AutoCloseable withActivity(ITestDevice device, String pkgName, String activity,
+            @Nullable String actionKey, @Nullable String actionValue) throws Exception {
+        String intentString;
+        if (actionKey != null && actionValue != null) {
+            intentString = actionKey + " " + actionValue;
+        } else {
+            intentString = null;
+        }
+
+        String cmd = "am start -n " + pkgName + "/." + activity;
+        if (intentString != null) {
+            cmd += " -e " + intentString;
+        }
+        device.executeShellCommand(cmd);
+
+        return () -> {
+            device.executeShellCommand("am force-stop " + pkgName);
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        };
+    }
+
+}
diff --git a/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
index 6fa2f2b..1217fbe 100644
--- a/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
+++ b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
@@ -142,55 +142,53 @@
             Set<Long> enabledChanges, Set<Long> disabledChanges,
             Set<Long> reportedEnabledChanges, Set<Long> reportedDisabledChanges)
             throws DeviceNotAvailableException {
+
         // Set compat overrides
         setCompatConfig(enabledChanges, disabledChanges, pkgName);
-
         // Send statsd config
         final long configId = getClass().getCanonicalName().hashCode();
         createAndUploadStatsdConfig(configId, pkgName);
 
-        // Run device-side test
-        if (testClassName.startsWith(".")) {
-            testClassName = pkgName + testClassName;
-        }
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, TEST_RUNNER,
-                getDevice().getIDevice());
-        testRunner.setMethodName(testClassName, testMethodName);
-        CollectingTestListener listener = new CollectingTestListener();
-        assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
-
-        // Clear overrides.
-        resetCompatChanges(enabledChanges, pkgName);
-        resetCompatChanges(disabledChanges, pkgName);
-
-        // Clear statsd report data and remove config
-        Map<Long, Boolean> reportedChanges = getReportedChanges(configId, pkgName);
-        removeStatsdConfig(configId);
-
-        // Check that device side test occurred as expected
-        final TestRunResult result = listener.getCurrentRunResults();
-        assertWithMessage("Failed to successfully run device tests for %s: %s",
-                          result.getName(), result.getRunFailureMessage())
-                .that(result.isRunFailure()).isFalse();
-        assertWithMessage("Should run only exactly one test method!")
-                .that(result.getNumTests()).isEqualTo(1);
-        if (result.hasFailedTests()) {
-            // build a meaningful error message
-            StringBuilder errorBuilder = new StringBuilder("On-device test failed:\n");
-            for (Map.Entry<TestDescription, TestResult> resultEntry :
-                    result.getTestResults().entrySet()) {
-                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
-                    errorBuilder.append(resultEntry.getKey().toString());
-                    errorBuilder.append(":\n");
-                    errorBuilder.append(resultEntry.getValue().getStackTrace());
-                }
+        try {
+            // Run device-side test
+            if (testClassName.startsWith(".")) {
+                testClassName = pkgName + testClassName;
             }
-            throw new AssertionError(errorBuilder.toString());
+            RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, TEST_RUNNER,
+                    getDevice().getIDevice());
+            testRunner.setMethodName(testClassName, testMethodName);
+            CollectingTestListener listener = new CollectingTestListener();
+            assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
+
+            // Check that device side test occurred as expected
+            final TestRunResult result = listener.getCurrentRunResults();
+            assertWithMessage("Failed to successfully run device tests for %s: %s",
+                            result.getName(), result.getRunFailureMessage())
+                    .that(result.isRunFailure()).isFalse();
+            assertWithMessage("Should run only exactly one test method!")
+                    .that(result.getNumTests()).isEqualTo(1);
+            if (result.hasFailedTests()) {
+                // build a meaningful error message
+                StringBuilder errorBuilder = new StringBuilder("On-device test failed:\n");
+                for (Map.Entry<TestDescription, TestResult> resultEntry :
+                        result.getTestResults().entrySet()) {
+                    if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+                        errorBuilder.append(resultEntry.getKey().toString());
+                        errorBuilder.append(":\n");
+                        errorBuilder.append(resultEntry.getValue().getStackTrace());
+                    }
+                }
+                throw new AssertionError(errorBuilder.toString());
+            }
+
+        } finally {
+            // Cleanup compat overrides
+            resetCompatConfig(pkgName, enabledChanges, disabledChanges);
+            // Validate statsd report
+            validatePostRunStatsdReport(configId, pkgName, reportedEnabledChanges,
+                                        reportedDisabledChanges);
         }
 
-        // Validate statsd report
-        validatePostRunStatsdReport(reportedChanges, reportedEnabledChanges,
-            reportedDisabledChanges);
     }
 
     /**
@@ -217,7 +215,7 @@
      * @param pkgName  The package name of the app that is expected to report the atom. It will be
      *                 the only allowed log source.
      */
-    private void createAndUploadStatsdConfig(long configId, String pkgName)
+    protected void createAndUploadStatsdConfig(long configId, String pkgName)
             throws DeviceNotAvailableException {
         final String atomName = "Atom" + System.nanoTime();
         final String eventName = "Event" + System.nanoTime();
@@ -252,6 +250,8 @@
         } catch (IOException e) {
             throw new RuntimeException("IO error when writing to temp file.", e);
         }
+        // Purge data
+        getReportList(configId);
     }
 
     /**
@@ -278,7 +278,7 @@
      * @param disabledChanges Changes to be disabled.
      * @param packageName     Package name for the app whose config is being changed.
      */
-    private void setCompatConfig(Set<Long> enabledChanges, Set<Long> disabledChanges,
+    protected void setCompatConfig(Set<Long> enabledChanges, Set<Long> disabledChanges,
             @Nonnull String packageName) throws DeviceNotAvailableException {
         for (Long enabledChange : enabledChanges) {
             runCommand("am compat enable " + enabledChange + " " + packageName);
@@ -291,7 +291,7 @@
     /**
      * Reset changes to default for a package.
      */
-    private void resetCompatChanges(Set<Long> changes, @Nonnull String packageName)
+    protected void resetCompatChanges(Set<Long> changes, @Nonnull String packageName)
             throws DeviceNotAvailableException {
         for (Long change : changes) {
             runCommand("am compat reset " + change + " " + packageName);
@@ -332,16 +332,41 @@
     }
 
     /**
-     * Validate that all overridden changes were logged while running the test.
+     * Cleanup the altered change ids under test.
+     *
+     * @param pkgName               Package name of the app under test.
+     * @param enabledChanges        Set of changes that were enabled during the test and need to be
+     *                              reset to the default value.
+     * @param disabledChanges       Set of changes that were disabled during the test and need to
+     *                              be reset to the default value.
      */
-    private void validatePostRunStatsdReport(Map<Long, Boolean> reportedChanges,
-            Set<Long> enabledChanges, Set<Long> disabledChanges)
+    protected void resetCompatConfig( String pkgName, Set<Long> enabledChanges,
+            Set<Long> disabledChanges) throws DeviceNotAvailableException {
+        // Clear overrides.
+        resetCompatChanges(enabledChanges, pkgName);
+        resetCompatChanges(disabledChanges, pkgName);
+    }
+
+    /**
+     * Validate that all overridden changes were logged while running the test.
+     *
+     * @param configId              The unique config id used to track change id queries.
+     * @param pkgName               Package name of the app under test.
+     * @param loggedEnabledChanges  Changes expected to be logged as enabled during the test.
+     * @param loggedDisabledChanges Changes expected to be logged as disabled during the test.
+     */
+    protected void validatePostRunStatsdReport(long configId, String pkgName,
+            Set<Long> loggedEnabledChanges, Set<Long> loggedDisabledChanges)
             throws DeviceNotAvailableException {
-        for (Long enabledChange : enabledChanges) {
+        // Clear statsd report data and remove config
+        Map<Long, Boolean> reportedChanges = getReportedChanges(configId, pkgName);
+        removeStatsdConfig(configId);
+
+        for (Long enabledChange : loggedEnabledChanges) {
             assertThat(reportedChanges)
                     .containsEntry(enabledChange, true);
         }
-        for (Long disabledChange : disabledChanges) {
+        for (Long disabledChange : loggedDisabledChanges) {
             assertThat(reportedChanges)
                     .containsEntry(disabledChange, false);
         }
diff --git a/hostsidetests/appcompat/strictjavapackages/TEST_MAPPING b/hostsidetests/appcompat/strictjavapackages/TEST_MAPPING
new file mode 100644
index 0000000..70dfc61
--- /dev/null
+++ b/hostsidetests/appcompat/strictjavapackages/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsStrictJavaPackagesTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index cbfad62..de3e704 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -19,6 +19,8 @@
 per-file DocumentsTest.java = zemiao@google.com
 per-file EphemeralTest.java = toddke@google.com
 per-file ExternalStorageHostTest.java = jsharkey@google.com
+per-file ExternalStorageHostTest.java = nandana@google.com
+per-file ExternalStorageHostTest.java = zezeozue@google.com
 per-file InstantAppUserTest.java = toddke@google.com
 per-file InstantCookieHostTest.java = toddke@google.com
 per-file IsolatedSplitsTests.java = toddke@google.com
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
index 80f4c3f..d1507df 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
@@ -90,7 +90,7 @@
                 .runExpectingFailure();
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallBaseWithSplit()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -103,7 +103,7 @@
         verifyFsverityInstall(BASE_APK, SPLIT_APK);
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallBaseWithDm() throws DeviceNotAvailableException, FileNotFoundException {
         new InstallMultiple()
@@ -115,7 +115,7 @@
         verifyFsverityInstall(BASE_APK, BASE_APK_DM);
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallEverything() throws DeviceNotAvailableException, FileNotFoundException {
         new InstallMultiple()
@@ -131,7 +131,7 @@
         verifyFsverityInstall(BASE_APK, BASE_APK_DM, SPLIT_APK, SPLIT_APK_DM);
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallSplitOnly()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -149,7 +149,7 @@
         verifyFsverityInstall(BASE_APK, SPLIT_APK);
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallSplitOnlyMissingSignature()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -165,7 +165,7 @@
                 .runExpectingFailure();
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallSplitOnlyWithoutBaseSignature()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -181,7 +181,7 @@
         verifyFsverityInstall(SPLIT_APK);
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallOnlyBaseHasFsvSig()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -194,7 +194,7 @@
                 .runExpectingFailure();
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallOnlyDmHasFsvSig()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -207,7 +207,7 @@
                 .runExpectingFailure();
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallOnlySplitHasFsvSig()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -220,7 +220,7 @@
                 .runExpectingFailure();
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallBaseWithFsvSigThenSplitWithout()
             throws DeviceNotAvailableException, FileNotFoundException {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
index cd43821..d536ade 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
@@ -144,6 +144,12 @@
         }
     }
 
+    public void testAfterMoveDocumentInStorage_revokeUriPermission() throws Exception {
+        runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
+                "testAfterMoveDocumentInStorage_revokeUriPermission");
+
+    }
+
     private boolean isAtLeastR() {
         try {
             String apiString = getDevice().getProperty("ro.build.version.sdk");
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
index fbc01df..b63d5ae 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
@@ -17,11 +17,11 @@
 package android.appsecurity.cts;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 
 import android.platform.test.annotations.AppModeFull;
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
@@ -507,6 +507,35 @@
                 "testFullApplicationReadFile");
     }
 
+    @Test
+    public void testGetChangedPackages() throws Throwable {
+        if (mIsUnsupportedDevice) {
+            return;
+        }
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), NORMAL_PKG, TEST_CLASS,
+                "testGetChangedPackages");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testGetChangedPackages");
+    }
+
+    @Test
+    public void testUninstall_noExtraRemovedBySystemInPackageRemovedIntent() throws Throwable {
+        assumeFalse("Device does not support instant app", mIsUnsupportedDevice);
+        installEphemeralApp(EPHEMERAL_1_APK, NORMAL_PKG);
+
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), NORMAL_PKG, TEST_CLASS,
+                "testUninstall_noExtraRemovedBySystemInPackageRemovedIntent");
+    }
+
+    @Test
+    public void testPruneInstantApp_hasExtraRemovedBySystemInPackageRemovedIntent()
+            throws Throwable {
+        assumeFalse("Device does not support instant app", mIsUnsupportedDevice);
+
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), NORMAL_PKG, TEST_CLASS,
+                "testPruneInstantApp_hasExtraRemovedBySystemInPackageRemovedIntent");
+    }
+
     private static final HashMap<String, String> makeArgs(
             String action, String category, String mimeType) {
         if (action == null || action.length() == 0) {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index 2a087dc..f2d07d1 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -473,19 +473,15 @@
     }
 
     @Test
-    public void testMediaSandboxed() throws Exception {
-        doMediaSandboxed(MEDIA, true);
+    public void testMediaLegacy28() throws Exception {
+        doMediaLegacy(MEDIA_28);
     }
     @Test
-    public void testMediaSandboxed28() throws Exception {
-        doMediaSandboxed(MEDIA_28, false);
-    }
-    @Test
-    public void testMediaSandboxed29() throws Exception {
-        doMediaSandboxed(MEDIA_29, false);
+    public void testMediaLegacy29() throws Exception {
+        doMediaLegacy(MEDIA_29);
     }
 
-    private void doMediaSandboxed(Config config, boolean sandboxed) throws Exception {
+    private void doMediaLegacy(Config config) throws Exception {
         installPackage(config.apk);
         installPackage(MEDIA_29.apk);
         // Make sure user initialization is complete before updating permission
@@ -503,17 +499,47 @@
             // Create the files needed for the test from MEDIA_29 pkg since shell
             // can't access secondary user's storage.
             runDeviceTests(MEDIA_29.pkg, MEDIA_29.clazz, "testStageFiles", user);
-
-            if (sandboxed) {
-                runDeviceTests(config.pkg, config.clazz, "testSandboxed", user);
-            } else {
-                runDeviceTests(config.pkg, config.clazz, "testNotSandboxed", user);
-            }
-
+            runDeviceTests(config.pkg, config.clazz, "testLegacy", user);
             runDeviceTests(MEDIA_29.pkg, MEDIA_29.clazz, "testClearFiles", user);
         }
     }
 
+
+    @Test
+    public void testGrantUriPermission() throws Exception {
+        doGrantUriPermission(MEDIA, "testGrantUriPermission", new String[]{},
+                new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE});
+        doGrantUriPermission(MEDIA, "testGrantUriPermission",
+                new String[]{PERM_READ_EXTERNAL_STORAGE},
+                new String[]{PERM_WRITE_EXTERNAL_STORAGE});
+        doGrantUriPermission(MEDIA, "testGrantUriPermission",
+                new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE},
+                new String[] {});
+    }
+
+    @Test
+    public void testGrantUriPermission29() throws Exception {
+        doGrantUriPermission(MEDIA_29, "testGrantUriPermission", new String[]{},
+                new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE});
+        doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
+                new String[]{PERM_READ_EXTERNAL_STORAGE},
+                new String[]{PERM_WRITE_EXTERNAL_STORAGE});
+        doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
+                new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE},
+                new String[] {});
+    }
+
+    private void doGrantUriPermission(Config config, String method, String[] grantPermissions,
+            String[] revokePermissions) throws Exception {
+        uninstallPackage(config.apk);
+        installPackage(config.apk);
+        for (int user : mUsers) {
+            updatePermissions(config.pkg, user, grantPermissions, true);
+            updatePermissions(config.pkg, user, revokePermissions, false);
+            runDeviceTests(config.pkg, config.clazz, method, user);
+        }
+    }
+
     @Test
     public void testMediaNone() throws Exception {
         doMediaNone(MEDIA);
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
index af9d4d2..9b0a243 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
@@ -108,7 +108,7 @@
         runDeviceTests(TEST_PKG, ".PrivilegedUpdateTest", "testPrivilegedAppPriorities");
     }
 
-    public void testPrivilegedAppUpgradePriorities() throws Exception {
+    public void testPrivilegedAppUpgradePrioritiesPreservedOnReboot() throws Exception {
         if (!isDefaultAbi()) {
             Log.w(TAG, "Skipping test for non-default abi.");
             return;
@@ -120,6 +120,10 @@
             assertNull(getDevice().installPackage(
                     mBuildHelper.getTestFile(SHIM_UPDATE_APK), true));
             runDeviceTests(TEST_PKG, ".PrivilegedUpdateTest", "testPrivilegedAppUpgradePriorities");
+
+            getDevice().reboot();
+
+            runDeviceTests(TEST_PKG, ".PrivilegedUpdateTest", "testPrivilegedAppUpgradePriorities");
         } finally {
             getDevice().uninstallPackage(SHIM_PKG);
         }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
index 557596a..8433aa7 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
@@ -112,9 +112,6 @@
                 deviceClearLskf();
             } finally {
                 removeTestPackages();
-
-                getDevice().rebootUntilOnline();
-                getDevice().waitForDeviceAvailable();
             }
         }
     }
@@ -159,9 +156,6 @@
                 deviceClearLskf();
             } finally {
                 removeTestPackages();
-
-                getDevice().rebootUntilOnline();
-                getDevice().waitForDeviceAvailable();
             }
         }
     }
@@ -217,9 +211,6 @@
                 deviceClearLskf();
             } finally {
                 removeTestPackages();
-
-                getDevice().rebootUntilOnline();
-                getDevice().waitForDeviceAvailable();
             }
         }
     }
@@ -277,9 +268,6 @@
                 deviceClearLskf();
             } finally {
                 removeTestPackages();
-
-                getDevice().rebootUntilOnline();
-                getDevice().waitForDeviceAvailable();
             }
         }
     }
@@ -339,7 +327,7 @@
 
     private void deviceRebootAndApply() throws Exception {
         String res = getDevice().executeShellCommand("cmd recovery reboot-and-apply cts-test1 cts-test");
-        if (res != null && res.contains("failure")) {
+        if (res != null && res.contains("Reboot and apply status: failure")) {
             fail("could not call reboot-and-apply");
         }
 
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/Android.bp b/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/Android.bp
index aca31e7..3786e75 100644
--- a/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/Android.bp
@@ -33,6 +33,11 @@
     use_embedded_native_libs: true,
     sdk_version: "current",
     certificate: ":cts-testkey1",
+    dist: {
+        targets: [
+            "cts",
+        ],
+    },
 }
 
 android_test_helper_app {
@@ -48,6 +53,11 @@
     },
     sdk_version: "current",
     certificate: ":cts-testkey1",
+    dist: {
+        targets: [
+            "cts",
+        ],
+    },
 }
 
 cc_library_shared {
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/OWNERS b/hostsidetests/appsecurity/test-apps/AppAccessData/OWNERS
new file mode 100644
index 0000000..a29e20e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 315013
+alanstokes@google.com
+nandana@google.com
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
index b00dcc3..58d0345 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
@@ -16,8 +16,8 @@
     name: "CtsAppDataIsolationAppA",
     defaults: ["cts_support_defaults"],
     srcs: ["common/src/**/*.java", "AppA/src/**/*.java", "AppA/aidl/**/*.aidl"],
-    sdk_version: "current",
-    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator"],
+    sdk_version: "test_current",
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator", "compatibility-device-util-axt"],
     libs: ["android.test.base"],
     // tag this module as a cts test artifact
     test_suites: [
@@ -35,8 +35,8 @@
     name: "CtsAppDataIsolationAppSharedA",
     defaults: ["cts_support_defaults"],
     srcs: ["common/src/**/*.java", "AppA/src/**/*.java", "AppA/aidl/**/*.aidl"],
-    sdk_version: "current",
-    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator"],
+    sdk_version: "test_current",
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator", "compatibility-device-util-axt"],
     libs: ["android.test.base"],
     // tag this module as a cts test artifact
     test_suites: [
@@ -54,8 +54,8 @@
     name: "CtsAppDataIsolationAppDirectBootA",
     defaults: ["cts_support_defaults"],
     srcs: ["common/src/**/*.java", "AppA/src/**/*.java", "AppA/aidl/**/*.aidl"],
-    sdk_version: "current",
-    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator"],
+    sdk_version: "test_current",
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator", "compatibility-device-util-axt"],
     libs: ["android.test.base"],
     // tag this module as a cts test artifact
     test_suites: [
@@ -73,8 +73,8 @@
     name: "CtsAppDataIsolationAppB",
     defaults: ["cts_support_defaults"],
     srcs: ["common/src/**/*.java", "AppB/src/**/*.java"],
-    sdk_version: "current",
-    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng"],
+    sdk_version: "test_current",
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator", "compatibility-device-util-axt"],
     libs: ["android.test.base"],
     // tag this module as a cts test artifact
     test_suites: [
@@ -92,7 +92,7 @@
     name: "CtsAppDataIsolationAppSharedB",
     defaults: ["cts_support_defaults"],
     srcs: ["common/src/**/*.java", "AppB/src/**/*.java"],
-    sdk_version: "current",
+    sdk_version: "test_current",
     static_libs: ["androidx.test.rules", "truth-prebuilt", "testng"],
     libs: ["android.test.base"],
     // tag this module as a cts test artifact
@@ -105,4 +105,4 @@
         enabled: false,
     },
     manifest: "AppB/AndroidManifest_shared.xml",
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/IsolatedService.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/IsolatedService.java
index 5b1ec66..d60cc56 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/IsolatedService.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/IsolatedService.java
@@ -22,9 +22,12 @@
 import android.app.Service;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 
+import com.android.compatibility.common.util.PropertyUtil;
 import com.android.cts.appdataisolation.common.FileUtils;
 
 public class IsolatedService extends Service {
@@ -35,25 +38,16 @@
         public void assertDataIsolated() throws RemoteException {
             try {
                 ApplicationInfo applicationInfo = getApplicationInfo();
-                assertDirDoesNotExist(applicationInfo.dataDir);
-                assertDirDoesNotExist(applicationInfo.deviceProtectedDataDir);
-                assertDirDoesNotExist("/data/data/" + getPackageName());
-                assertDirDoesNotExist("/data/misc/profiles/cur/0/" + getPackageName());
+
                 assertDirIsNotAccessible("/data/misc/profiles/ref");
-
-                assertDirDoesNotExist(FileUtils.replacePackageAWithPackageB(
-                        applicationInfo.dataDir));
-                assertDirDoesNotExist(FileUtils.replacePackageAWithPackageB(
-                        applicationInfo.deviceProtectedDataDir));
-                assertDirDoesNotExist("/data/data/" + FileUtils.APPB_PKG);
-                assertDirDoesNotExist("/data/misc/profiles/cur/0/" + FileUtils.APPB_PKG);
-
-                assertDirDoesNotExist(FileUtils.replacePackageAWithNotInstalledPkg(
-                        applicationInfo.dataDir));
-                assertDirDoesNotExist(FileUtils.replacePackageAWithNotInstalledPkg(
-                        applicationInfo.deviceProtectedDataDir));
-                assertDirDoesNotExist("/data/data/" + FileUtils.NOT_INSTALLED_PKG);
-                assertDirDoesNotExist("/data/misc/profiles/cur/0/" + FileUtils.NOT_INSTALLED_PKG);
+                if (isVendorPolicyNewerThanR()) {
+                    // Restrictions are enforced in SELinux, so EACCESS is returned
+                    assertDirectoriesInaccessible(applicationInfo);
+                } else {
+                    // Vendor partition has older, more permissive SELinux policy.
+                    // Restrictions are enforced via app data isolation, so ENOENT is returned
+                    assertDirectoriesDoNotExist(applicationInfo);
+                }
             } catch (Throwable e) {
                 throw new IllegalStateException(e.getMessage());
             }
@@ -61,6 +55,56 @@
 
     };
 
+    private boolean isVendorPolicyNewerThanR() {
+        if (SystemProperties.get("ro.vndk.version").equals("S")) {
+            // Vendor build is S, but before the API level bump - good enough for us.
+            return true;
+        }
+        return PropertyUtil.isVendorApiLevelNewerThan(Build.VERSION_CODES.R);
+    }
+
+    private void assertDirectoriesInaccessible(ApplicationInfo applicationInfo) {
+        assertDirIsNotAccessible(applicationInfo.dataDir);
+        assertDirIsNotAccessible(applicationInfo.deviceProtectedDataDir);
+        assertDirIsNotAccessible("/data/data/" + getPackageName());
+        assertDirIsNotAccessible("/data/misc/profiles/cur/0/" + getPackageName());
+
+        assertDirIsNotAccessible(FileUtils.replacePackageAWithPackageB(
+                applicationInfo.dataDir));
+        assertDirIsNotAccessible(FileUtils.replacePackageAWithPackageB(
+                applicationInfo.deviceProtectedDataDir));
+        assertDirIsNotAccessible("/data/data/" + FileUtils.APPB_PKG);
+        assertDirIsNotAccessible("/data/misc/profiles/cur/0/" + FileUtils.APPB_PKG);
+
+        assertDirIsNotAccessible(FileUtils.replacePackageAWithNotInstalledPkg(
+                applicationInfo.dataDir));
+        assertDirIsNotAccessible(FileUtils.replacePackageAWithNotInstalledPkg(
+                applicationInfo.deviceProtectedDataDir));
+        assertDirIsNotAccessible("/data/data/" + FileUtils.NOT_INSTALLED_PKG);
+        assertDirIsNotAccessible("/data/misc/profiles/cur/0/" + FileUtils.NOT_INSTALLED_PKG);
+    }
+
+    private void assertDirectoriesDoNotExist(ApplicationInfo applicationInfo) {
+        assertDirDoesNotExist(applicationInfo.dataDir);
+        assertDirDoesNotExist(applicationInfo.deviceProtectedDataDir);
+        assertDirDoesNotExist("/data/data/" + getPackageName());
+        assertDirDoesNotExist("/data/misc/profiles/cur/0/" + getPackageName());
+
+        assertDirDoesNotExist(FileUtils.replacePackageAWithPackageB(
+                applicationInfo.dataDir));
+        assertDirDoesNotExist(FileUtils.replacePackageAWithPackageB(
+                applicationInfo.deviceProtectedDataDir));
+        assertDirDoesNotExist("/data/data/" + FileUtils.APPB_PKG);
+        assertDirDoesNotExist("/data/misc/profiles/cur/0/" + FileUtils.APPB_PKG);
+
+        assertDirDoesNotExist(FileUtils.replacePackageAWithNotInstalledPkg(
+                applicationInfo.dataDir));
+        assertDirDoesNotExist(FileUtils.replacePackageAWithNotInstalledPkg(
+                applicationInfo.deviceProtectedDataDir));
+        assertDirDoesNotExist("/data/data/" + FileUtils.NOT_INSTALLED_PKG);
+        assertDirDoesNotExist("/data/misc/profiles/cur/0/" + FileUtils.NOT_INSTALLED_PKG);
+    }
+
     @Override
     public IBinder onBind(Intent intent) {
         return mBinder;
diff --git a/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/AndroidManifest.xml
index 05676a8..6056e70 100644
--- a/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/AndroidManifest.xml
@@ -15,21 +15,23 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.authboundkeyapp">
+     package="com.android.cts.authboundkeyapp">
 
-    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
+    <uses-sdk android:minSdkVersion="21"
+         android:targetSdkVersion="27"/>
 
     <application android:label="AuthBoundKeyApp">
-        <activity android:name=".BaseActivity">
+        <activity android:name=".BaseActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.authboundkeyapp" />
+         android:targetPackage="com.android.cts.authboundkeyapp"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index 106434f..14f492c 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -16,13 +16,19 @@
 
 package com.android.cts.documentclient;
 
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+
 import android.app.Activity;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.SystemClock;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
@@ -37,8 +43,12 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.cts.documentclient.MyActivity.Result;
 
+import java.io.File;
 import java.util.List;
 
 /**
@@ -47,6 +57,19 @@
  */
 public class DocumentsClientTest extends DocumentsClientTestCase {
     private static final String TAG = "DocumentsClientTest";
+    private static final String DOWNLOAD_PATH =
+            Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar
+                    + Environment.DIRECTORY_DOWNLOADS;
+    private static final String TEST_DESTINATION_DIRECTORY_NAME = "TEST_PERMISSION_DESTINATION";
+    private static final String TEST_DESTINATION_DIRECTORY_PATH =
+            DOWNLOAD_PATH + File.separatorChar + TEST_DESTINATION_DIRECTORY_NAME;
+    private static final String TEST_SOURCE_DIRECTORY_NAME = "TEST_PERMISSION_SOURCE";
+    private static final String TEST_SOURCE_DIRECTORY_PATH =
+            DOWNLOAD_PATH + File.separatorChar + TEST_SOURCE_DIRECTORY_NAME;
+    private static final String TEST_TARGET_DIRECTORY_NAME = "TEST_TARGET";
+    private static final String TEST_TARGET_DIRECTORY_PATH =
+            TEST_SOURCE_DIRECTORY_PATH + File.separatorChar + TEST_TARGET_DIRECTORY_NAME;
+    private static final String STORAGE_AUTHORITY = "com.android.externalstorage.documents";
 
     private UiSelector findRootListSelector() throws UiObjectNotFoundException {
         return new UiSelector().resourceId(
@@ -140,6 +163,18 @@
         assertTrue(title.waitForExists(TIMEOUT));
     }
 
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        deleteTestDirectory();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        deleteTestDirectory();
+    }
+
     public void testOpenSimple() throws Exception {
         if (!supportedHardware()) return;
 
@@ -815,4 +850,75 @@
             // expected
         }
     }
+
+    public void testAfterMoveDocumentInStorage_revokeUriPermission() throws Exception {
+        if (!supportedHardware()) return;
+
+        final Context context = getInstrumentation().getContext();
+        final Uri initUri = DocumentsContract.buildDocumentUri(STORAGE_AUTHORITY,
+                "primary:" + Environment.DIRECTORY_DOWNLOADS);
+
+        // create the source directory
+        final Uri sourceUri = assertCreateDocumentSuccess(initUri, TEST_SOURCE_DIRECTORY_NAME,
+                Document.MIME_TYPE_DIR);
+
+        // create the target directory
+        final Uri targetUri = assertCreateDocumentSuccess(sourceUri, TEST_TARGET_DIRECTORY_NAME,
+                Document.MIME_TYPE_DIR);
+        final int permissionFlag = FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION;
+
+        // check permission for the target uri
+        assertEquals(PackageManager.PERMISSION_GRANTED,
+                context.checkCallingOrSelfUriPermission(targetUri, permissionFlag));
+
+        // create the destination directory
+        final Uri destinationUri = assertCreateDocumentSuccess(initUri,
+                TEST_DESTINATION_DIRECTORY_NAME, Document.MIME_TYPE_DIR);
+
+        final ContentResolver resolver = context.getContentResolver();
+        final Uri movedFileUri = DocumentsContract.moveDocument(resolver, targetUri, sourceUri,
+                destinationUri);
+        assertTrue(movedFileUri != null);
+
+        // after moving the document,  the permission of targetUri is revoked
+        assertEquals(PackageManager.PERMISSION_DENIED,
+                context.checkCallingOrSelfUriPermission(targetUri, permissionFlag));
+
+        // create the target directory again, it still has no permission for targetUri
+        executeShellCommand("mkdir " + TEST_TARGET_DIRECTORY_PATH);
+
+        assertEquals(PackageManager.PERMISSION_DENIED,
+                context.checkCallingOrSelfUriPermission(targetUri, permissionFlag));
+    }
+
+    private Uri assertCreateDocumentSuccess(@Nullable Uri initUri, @NonNull String displayName,
+            @NonNull String mimeType) throws Exception {
+        // Clear DocsUI's storage to avoid it opening stored last location.
+        clearDocumentsUi();
+
+        // create document
+        final Intent createIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+        createIntent.addCategory(Intent.CATEGORY_OPENABLE);
+        createIntent.putExtra(Intent.EXTRA_TITLE, displayName);
+        createIntent.setType(mimeType);
+        if (initUri != null) {
+            createIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initUri);
+        }
+        mActivity.startActivityForResult(createIntent, REQUEST_CODE);
+
+        mDevice.waitForIdle();
+        findSaveButton().click();
+
+        // check result
+        final Uri uri = mActivity.getResult().data.getData();
+        assertEquals(displayName, getColumn(uri, Document.COLUMN_DISPLAY_NAME));
+        assertEquals(mimeType, getColumn(uri, Document.COLUMN_MIME_TYPE));
+
+        return uri;
+    }
+
+    private void deleteTestDirectory() throws Exception{
+        executeShellCommand("rm -rf " + TEST_DESTINATION_DIRECTORY_PATH);
+        executeShellCommand("rm -rf " + TEST_SOURCE_DIRECTORY_PATH);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
index 86b134c..b5ea025 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
@@ -15,30 +15,32 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.documentprovider">
+     package="com.android.cts.documentprovider">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <application>
         <uses-library android:name="android.test.runner"/>
 
         <provider android:name=".MyDocumentsProvider"
-                android:authorities="com.android.cts.documentprovider"
-                android:exported="true"
-                android:grantUriPermissions="true"
-                android:permission="android.permission.MANAGE_DOCUMENTS">
+             android:authorities="com.android.cts.documentprovider"
+             android:exported="true"
+             android:grantUriPermissions="true"
+             android:permission="android.permission.MANAGE_DOCUMENTS">
             <intent-filter>
-                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER"/>
             </intent-filter>
         </provider>
 
         <activity android:name=".GetContentActivity"
-                android:label="CtsGetContent">
+             android:label="CtsGetContent"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.GET_CONTENT" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.OPENABLE" />
-                <data android:mimeType="image/*" />
+                <action android:name="android.intent.action.GET_CONTENT"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.OPENABLE"/>
+                <data android:mimeType="image/*"/>
             </intent-filter>
         </activity>
 
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
index ea12f85..841e7b2 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
@@ -15,118 +15,114 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-  package="com.android.cts.ephemeralapp1" >
-    <uses-sdk
-        android:minSdkVersion="24" android:targetSdkVersion="26" />
+     package="com.android.cts.ephemeralapp1">
+    <uses-sdk android:minSdkVersion="24"
+         android:targetSdkVersion="26"/>
 
-    <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
+    <uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
-    <uses-permission android:name="android.permission.VIBRATE" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
 
-    <application
-        android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
-        <activity
-            android:name=".EphemeralActivity"
-            android:theme="@android:style/Theme.NoDisplay" >
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".EphemeralActivity"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: normal app can start w/o knowing about this activity -->
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="https" />
-                <data android:host="cts.google.com" />
-                <data android:path="/ephemeral" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="cts.google.com"/>
+                <data android:path="/ephemeral"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can see this activity using query methods -->
             <!-- TEST: normal apps can't see this activity using query methods -->
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can start this activity using directed intent -->
             <!-- TEST: normal apps can't start this activity using directed intent -->
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
+                <action android:name="android.intent.action.SEARCH"/>
             </intent-filter>
             <meta-data android:name="default-url"
-                       android:value="https://ephemeralapp1.cts.android.com/search" />
-            <meta-data
-                       android:name="android.app.searchable"
-                       android:resource="@xml/searchable" />
+                 android:value="https://ephemeralapp1.cts.android.com/search"/>
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable"/>
         </activity>
-        <activity
-            android:name=".EphemeralResult"
-            android:theme="@android:style/Theme.NoDisplay" >
+        <activity android:name=".EphemeralResult"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: allow sending results from other instant apps -->
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="https" />
-                <data android:host="cts.google.com" />
-                <data android:path="/result" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="cts.google.com"/>
+                <data android:path="/result"/>
             </intent-filter>
         </activity>
         <provider android:name=".SearchSuggestionProvider"
-            android:authorities="com.android.cts.ephemeralapp1.Search" />
+             android:authorities="com.android.cts.ephemeralapp1.Search"/>
 
-        <activity
-            android:name=".EphemeralActivity2"
-            android:theme="@android:style/Theme.NoDisplay">
+        <activity android:name=".EphemeralActivity2"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: ephemeral apps can start this activity using directed intent -->
             <!-- TEST: normal apps can't start this activity using directed intent -->
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL_PRIVATE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL_PRIVATE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity
-            android:name=".EphemeralActivity3"
-            android:theme="@android:style/Theme.NoDisplay">
+        <activity android:name=".EphemeralActivity3"
+             android:theme="@android:style/Theme.NoDisplay">
             <!-- TEST: ephemeral apps can start this activity using directed intent -->
         </activity>
-        <activity android:name=".WebViewTestActivity" />
-        <service
-            android:name=".EphemeralService">
+        <activity android:name=".WebViewTestActivity"/>
+        <service android:name=".EphemeralService"
+             android:exported="true">
             <!-- TEST: ephemeral apps can see this service using query methods -->
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can start this service using directed intent -->
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <provider
-            android:name=".EphemeralProvider"
-            android:authorities="com.android.cts.ephemeralapp1.provider"
-            android:exported="true">
+        <provider android:name=".EphemeralProvider"
+             android:authorities="com.android.cts.ephemeralapp1.provider"
+             android:exported="true">
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
             </intent-filter>
         </provider>
         <service android:name=".SomeService"/>
 
-        <activity android:name=".StartForResultActivity" android:exported="false" />
+        <activity android:name=".StartForResultActivity"
+             android:exported="false"/>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.ephemeralapp1" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.ephemeralapp1"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
index 23593b0..c098921 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
@@ -29,6 +29,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertThrows;
 
 import android.Manifest;
@@ -44,6 +45,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ChangedPackages;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
@@ -1424,6 +1426,16 @@
         }
     }
 
+    /** Tests getting changed packages for instant app. */
+    @Test
+    public void testGetChangedPackages() {
+        final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        // Instant apps can't get changed packages.
+        final ChangedPackages changedPackages = pm.getChangedPackages(0);
+        assertNull(changedPackages);
+    }
+
     /** Returns {@code true} if the given filter handles all web URLs, regardless of host. */
     private boolean handlesAllWebData(IntentFilter filter) {
         return filter.hasCategory(Intent.CATEGORY_APP_BROWSER) ||
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
index 3d814d8..415b268 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
@@ -15,68 +15,63 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-  package="com.android.cts.ephemeralapp2" >
-    <uses-sdk
-        android:minSdkVersion="24" android:targetSdkVersion="26" />
+     package="com.android.cts.ephemeralapp2">
+    <uses-sdk android:minSdkVersion="24"
+         android:targetSdkVersion="26"/>
 
     <!-- TEST: exists only for testing ephemeral app1 can't see this app -->
-    <application
-        android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
-        <activity
-          android:name=".EphemeralActivity"
-          android:theme="@android:style/Theme.NoDisplay">
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".EphemeralActivity"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="https" />
-                <data android:host="cts.google.com" />
-                <data android:path="/other" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="cts.google.com"/>
+                <data android:path="/other"/>
             </intent-filter>
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.START_OTHER_EPHEMERAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_OTHER_EPHEMERAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
+                <action android:name="android.intent.action.SEARCH"/>
             </intent-filter>
             <meta-data android:name="default-url"
-                       android:value="https://ephemeralapp2.cts.android.com/search" />
-            <meta-data
-                       android:name="android.app.searchable"
-                       android:resource="@xml/searchable" />
+                 android:value="https://ephemeralapp2.cts.android.com/search"/>
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable"/>
         </activity>
         <provider android:name=".SearchSuggestionProvider"
-            android:authorities="com.android.cts.ephemeralapp2.Search" />
+             android:authorities="com.android.cts.ephemeralapp2.Search"/>
 
 
         <!-- This should still not be visible to other Instant Apps -->
-        <activity
-            android:name=".ExposedActivity"
-            android:visibleToInstantApps="true"
-            android:theme="@android:style/Theme.NoDisplay" />
+        <activity android:name=".ExposedActivity"
+             android:visibleToInstantApps="true"
+             android:theme="@android:style/Theme.NoDisplay"/>
 
         <!-- This should still not be visible to other Instant Apps -->
-        <provider
-            android:name=".EphemeralProvider"
-            android:authorities="com.android.cts.ephemeralapp2.provider"
-            android:exported="true">
+        <provider android:name=".EphemeralProvider"
+             android:authorities="com.android.cts.ephemeralapp2.provider"
+             android:exported="true">
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
             </intent-filter>
         </provider>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.ephemeralapp2" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.ephemeralapp2"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/AndroidManifest.xml
index d9136d5..33f2c4f 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/AndroidManifest.xml
@@ -15,29 +15,26 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.implicitapp">
-    <uses-sdk
-        android:minSdkVersion="24" />
+     package="com.android.cts.implicitapp">
+    <uses-sdk android:minSdkVersion="24"/>
 
-    <application
-        android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
-        <activity
-            android:name=".ImplicitActivity"
-            android:theme="@android:style/Theme.NoDisplay">
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".ImplicitActivity"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: implicitly exposes this activity to instant apps -->
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="https" />
-                <data android:host="cts.google.com" />
-                <data android:path="/implicit" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="cts.google.com"/>
+                <data android:path="/implicit"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.implicitapp" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.implicitapp"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.bp b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.bp
index b7801a9..63fb0c0 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.bp
@@ -20,6 +20,8 @@
     static_libs: [
         "cts-aia-util",
         "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "truth-prebuilt",
     ],
     libs: ["android.test.base"],
     // tag this module as a cts test artifact
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml
index e577260..6084e95 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml
@@ -15,123 +15,116 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.normalapp">
-    <uses-sdk
-        android:minSdkVersion="24" />
+     package="com.android.cts.normalapp">
+    <uses-sdk android:minSdkVersion="24"/>
 
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
-    <application
-        android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
-        <activity
-            android:name=".NormalActivity"
-            android:theme="@android:style/Theme.NoDisplay">
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".NormalActivity"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: ephemeral apps can't see this activity using query methods -->
             <intent-filter android:priority="-20">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can't start this activity using directed intent -->
             <intent-filter>
-                <action android:name="com.android.cts.ephemeraltest.START_NORMAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_NORMAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
+                <action android:name="android.intent.action.SEARCH"/>
             </intent-filter>
             <meta-data android:name="default-url"
-                       android:value="https://normalapp.cts.android.com/search" />
-            <meta-data
-                       android:name="android.app.searchable"
-                       android:resource="@xml/searchable" />
+                 android:value="https://normalapp.cts.android.com/search"/>
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable"/>
         </activity>
-        <activity
-            android:name=".NormalWebActivity"
-            android:theme="@android:style/Theme.NoDisplay">
+        <activity android:name=".NormalWebActivity"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: implicitly exposes this activity to ephemeral apps -->
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="https" />
-                <data android:host="cts.google.com" />
-                <data android:path="/normal" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="cts.google.com"/>
+                <data android:path="/normal"/>
             </intent-filter>
         </activity>
-        <activity
-            android:name=".ExposedActivity"
-            android:visibleToInstantApps="true"
-            android:theme="@android:style/Theme.NoDisplay">
+        <activity android:name=".ExposedActivity"
+             android:visibleToInstantApps="true"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
           <!-- TEST: ephemeral apps can see this activity using query methods -->
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can start this activity using directed intent -->
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.START_EXPOSED" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EXPOSED"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
+                <action android:name="android.intent.action.SEARCH"/>
             </intent-filter>
             <meta-data android:name="default-url"
-                       android:value="https://normalapp.cts.android.com/search" />
-            <meta-data
-                       android:name="android.app.searchable"
-                       android:resource="@xml/searchable" />
+                 android:value="https://normalapp.cts.android.com/search"/>
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable"/>
         </activity>
         <provider android:name=".SearchSuggestionProvider"
-            android:authorities="com.android.cts.normalapp.Search" />
+             android:authorities="com.android.cts.normalapp.Search"/>
 
-        <service
-            android:name=".NormalService">
+        <service android:name=".NormalService"
+             android:exported="true">
             <!-- TEST: ephemeral apps can't see this service using query methods -->
             <intent-filter android:priority="-20">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can't start this service using directed intent -->
             <intent-filter>
-                <action android:name="com.android.cts.ephemeraltest.START_NORMAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_NORMAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
-        <service
-            android:name=".ExposedService"
-            android:visibleToInstantApps="true">
+        <service android:name=".ExposedService"
+             android:visibleToInstantApps="true"
+             android:exported="true">
             <!-- TEST: ephemeral apps can see this service using query methods -->
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can start this service using directed intent -->
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.START_EXPOSED" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EXPOSED"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <provider
-            android:name=".NormalProvider"
-            android:authorities="com.android.cts.normalapp.provider"
-            android:exported="true">
+        <provider android:name=".NormalProvider"
+             android:authorities="com.android.cts.normalapp.provider"
+             android:exported="true">
             <intent-filter android:priority="-20">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
             </intent-filter>
         </provider>
-        <provider
-            android:name=".ExposedProvider"
-            android:authorities="com.android.cts.normalapp.exposed.provider"
-            android:visibleToInstantApps="true"
-            android:exported="true">
+        <provider android:name=".ExposedProvider"
+             android:authorities="com.android.cts.normalapp.exposed.provider"
+             android:visibleToInstantApps="true"
+             android:exported="true">
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
             </intent-filter>
         </provider>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.normalapp" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.normalapp"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java
index d7f5424..2076a97 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java
@@ -16,13 +16,19 @@
 
 package com.android.cts.normalapp;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
+import android.Manifest;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -30,14 +36,19 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ChangedPackages;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.net.Uri;
-import android.provider.Settings.Secure;
+import android.os.PatternMatcher;
+import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.cts.util.TestResult;
 
 import org.junit.After;
@@ -46,6 +57,8 @@
 import org.junit.runner.RunWith;
 
 import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -68,6 +81,10 @@
     private static final String EXTRA_ACTIVITY_RESULT =
             "com.android.cts.ephemeraltest.EXTRA_ACTIVITY_RESULT";
 
+    private static final String EPHEMERAL_1_PKG = "com.android.cts.ephemeralapp1";
+    private static final String INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD =
+            "installed_instant_app_min_cache_period";
+
     private BroadcastReceiver mReceiver;
     private final SynchronousQueue<TestResult> mResultQueue = new SynchronousQueue<>();
 
@@ -139,7 +156,7 @@
         // query activities; directed package
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
-            queryIntent.setPackage("com.android.cts.ephemeralapp1");
+            queryIntent.setPackage(EPHEMERAL_1_PKG);
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentActivities(queryIntent, 0 /*flags*/);
             assertThat(resolveInfo.size(), is(0));
@@ -149,7 +166,7 @@
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
             queryIntent.setComponent(
-                    new ComponentName("com.android.cts.ephemeralapp1",
+                    new ComponentName(EPHEMERAL_1_PKG,
                             "com.android.cts.ephemeralapp1.EphemeralActivity"));
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentActivities(queryIntent, 0 /*flags*/);
@@ -207,7 +224,7 @@
         // query services; directed package
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
-            queryIntent.setPackage("com.android.cts.ephemeralapp1");
+            queryIntent.setPackage(EPHEMERAL_1_PKG);
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentServices(queryIntent, 0 /*flags*/);
             assertThat(resolveInfo.size(), is(0));
@@ -217,7 +234,7 @@
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
             queryIntent.setComponent(
-                    new ComponentName("com.android.cts.ephemeralapp1",
+                    new ComponentName(EPHEMERAL_1_PKG,
                             "com.android.cts.ephemeralapp1.EphemeralService"));
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentServices(queryIntent, 0 /*flags*/);
@@ -273,7 +290,7 @@
         // query content providers; directed package
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
-            queryIntent.setPackage("com.android.cts.ephemeralapp1");
+            queryIntent.setPackage(EPHEMERAL_1_PKG);
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentContentProviders(queryIntent, 0 /*flags*/);
             assertThat(resolveInfo.size(), is(0));
@@ -283,7 +300,7 @@
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
             queryIntent.setComponent(
-                    new ComponentName("com.android.cts.ephemeralapp1",
+                    new ComponentName(EPHEMERAL_1_PKG,
                             "com.android.cts.ephemeralapp1.EphemeralProvider"));
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentContentProviders(queryIntent, 0 /*flags*/);
@@ -377,7 +394,7 @@
             InstrumentationRegistry.getContext().startActivity(
                     startEphemeralIntent, null /*options*/);
             final TestResult testResult = getResult();
-            assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
+            assertThat(EPHEMERAL_1_PKG, is(testResult.getPackageName()));
             assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction()));
         }
 
@@ -386,7 +403,7 @@
         try {
             final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1");
+            startEphemeralIntent.setPackage(EPHEMERAL_1_PKG);
             InstrumentationRegistry.getContext().startActivity(
                     startEphemeralIntent, null /*options*/);
             final TestResult testResult = getResult();
@@ -398,11 +415,11 @@
         {
             final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
-            startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1");
+            startEphemeralIntent.setPackage(EPHEMERAL_1_PKG);
             InstrumentationRegistry.getContext().startActivity(
                     startEphemeralIntent, null /*options*/);
             final TestResult testResult = getResult();
-            assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
+            assertThat(EPHEMERAL_1_PKG, is(testResult.getPackageName()));
             assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction()));
         }
 
@@ -411,7 +428,7 @@
             final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startEphemeralIntent.setComponent(
-                    new ComponentName("com.android.cts.ephemeralapp1",
+                    new ComponentName(EPHEMERAL_1_PKG,
                             "com.android.cts.ephemeralapp1.EphemeralActivity"));
             InstrumentationRegistry.getContext().startActivity(
                     startEphemeralIntent, null /*options*/);
@@ -429,7 +446,7 @@
             InstrumentationRegistry.getContext().startActivity(
                     startViewIntent, null /*options*/);
             final TestResult testResult = getResult();
-            assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
+            assertThat(EPHEMERAL_1_PKG, is(testResult.getPackageName()));
             assertThat("EphemeralActivity", is(testResult.getComponentName()));
             assertThat(Intent.ACTION_VIEW, is(testResult.getIntent().getAction()));
             assertThat(testResult.getIntent().getCategories(), hasItems(Intent.CATEGORY_BROWSABLE));
@@ -488,6 +505,122 @@
         }
     }
 
+    /** Tests getting changed packages for instant app. */
+    @Test
+    public void testGetChangedPackages() {
+        final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        // Query changed packages without permission, and we should only get normal apps.
+        final ChangedPackages changedPackages = pm.getChangedPackages(0);
+        assertThat(changedPackages.getPackageNames()).doesNotContain(EPHEMERAL_1_PKG);
+
+        // Query changed packages with permission, and we should be able to get ephemeral apps.
+        runWithShellPermissionIdentity(() -> {
+            final ChangedPackages changesInstantApp = pm.getChangedPackages(0);
+            assertThat(changesInstantApp.getPackageNames()).contains(EPHEMERAL_1_PKG);
+        }, Manifest.permission.ACCESS_INSTANT_APPS);
+    }
+
+    @Test
+    public void testUninstall_noExtraRemovedBySystemInPackageRemovedIntent() {
+        runWithShellPermissionIdentity(() -> {
+            final boolean removedBySystem = uninstallAndWaitForExtraRemovedBySystem(
+                    InstrumentationRegistry.getContext(), EPHEMERAL_1_PKG);
+
+            assertThat(removedBySystem).isFalse();
+        }, Manifest.permission.DELETE_PACKAGES, Manifest.permission.ACCESS_INSTANT_APPS);
+    }
+
+    @Test
+    public void testPruneInstantApp_hasExtraRemovedBySystemInPackageRemovedIntent() {
+        runWithShellPermissionIdentity(() -> {
+            final boolean removedBySystem = pruneInstantAppAndWaitForExtraRemovedBySystem(
+                    InstrumentationRegistry.getContext(), EPHEMERAL_1_PKG);
+
+            assertThat(removedBySystem).isTrue();
+        }, Manifest.permission.WRITE_SECURE_SETTINGS, Manifest.permission.ACCESS_INSTANT_APPS);
+    }
+
+    /**
+     * Uninstall the package and wait for the package removed intent.
+     *
+     * @return The value of {@link Intent#EXTRA_REMOVED_BY_SYSTEM} associated with the intent.
+     */
+    private boolean uninstallAndWaitForExtraRemovedBySystem(Context context, String packageName) {
+        final Runnable uninstall = () -> {
+            final PackageInstaller packageInstaller = context.getPackageManager()
+                    .getPackageInstaller();
+            packageInstaller.uninstall(packageName, null);
+        };
+
+        final Intent packageRemoved = executeAndWaitForPackageRemoved(
+                context, packageName, uninstall);
+        return packageRemoved.getBooleanExtra(Intent.EXTRA_REMOVED_BY_SYSTEM, false);
+    }
+
+    /**
+     * Runs the shell command {@code pm trim-caches} to invoke system to prune instant applications.
+     * Waits for the package removed intent and returns the extra filed.
+     *
+     * @return The value of {@link Intent#EXTRA_REMOVED_BY_SYSTEM} associated with the intent.
+     */
+    private boolean pruneInstantAppAndWaitForExtraRemovedBySystem(Context context,
+            String packageName) {
+        final String defaultPeriod = Settings.Global.getString(context.getContentResolver(),
+                INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD);
+        final Runnable trimCaches = () -> {
+            // Updates installed instant app minimum cache period to zero to ensure that system
+            // could uninstall instant apps when trim-caches is invoked.
+            Settings.Global.putInt(context.getContentResolver(),
+                    INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, 0);
+            SystemUtil.runShellCommand("pm trim-caches " + Long.MAX_VALUE + " internal");
+        };
+
+        try {
+            final Intent packageRemoved = executeAndWaitForPackageRemoved(
+                    context, packageName, trimCaches);
+            return packageRemoved.getBooleanExtra(Intent.EXTRA_REMOVED_BY_SYSTEM, false);
+        } finally {
+            Settings.Global.putString(context.getContentResolver(),
+                    INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, defaultPeriod);
+        }
+    }
+
+    /**
+     * Executes a command and waits for the package removed intent.
+     *
+     * @return The {@link Intent#ACTION_PACKAGE_REMOVED} associated with the given package name.
+     */
+    private Intent executeAndWaitForPackageRemoved(Context context, String packageName,
+            Runnable command) {
+        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addDataScheme("package");
+        filter.addDataSchemeSpecificPart(packageName, PatternMatcher.PATTERN_LITERAL);
+        final BlockingQueue<Intent> intentQueue = new LinkedBlockingQueue<>();
+        final BroadcastReceiver removedReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                try {
+                    intentQueue.put(intent);
+                } catch (InterruptedException e) {
+                    fail("Cannot add intent to intent blocking queue!");
+                }
+            }
+        };
+        context.registerReceiver(removedReceiver, filter);
+        try {
+            command.run();
+            final Intent intent = intentQueue.poll(60 /* timeout */, TimeUnit.SECONDS);
+            assertNotNull("Timed out to wait for package removed intent", intent);
+            return intent;
+        } catch (InterruptedException e) {
+            fail("Failed to get package removed intent: " + e.getMessage());
+        } finally {
+            context.unregisterReceiver(removedReceiver);
+        }
+        return null;
+    }
+
     private TestResult getResult() {
         final TestResult result;
         try {
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml
index b44e04a..c2dde3c 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml
@@ -15,25 +15,23 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.userapp"
-    android:targetSandboxVersion="2">
+     package="com.android.cts.userapp"
+     android:targetSandboxVersion="2">
 
-    <application
-        android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
-        <activity
-            android:name=".UserActivity"
-            android:directBootAware="true" >
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".UserActivity"
+             android:directBootAware="true"
+             android:exported="true">
             <!-- TEST: when installed as an instant app, normal apps can't query for it -->
             <!-- TEST: when installed as a full app, normal apps can query for it -->
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.instantappusertest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.instantappusertest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.userapp" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.userapp"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
index 6fab412..57320b3 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
@@ -15,32 +15,35 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.isolatedsplitapp"
-    android:isolatedSplits="true"
-    android:targetSandboxVersion="2">
+     package="com.android.cts.isolatedsplitapp"
+     android:isolatedSplits="true"
+     android:targetSandboxVersion="2">
 
     <application android:label="IsolatedSplitApp">
 
-        <activity android:name=".BaseActivity">
+        <activity android:name=".BaseActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        
-        <service android:name=".BaseService" android:exported="true" />
-        
-        <receiver android:name=".BaseReceiver">
+
+        <service android:name=".BaseService"
+             android:exported="true"/>
+
+        <receiver android:name=".BaseReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.isolatedsplitapp.ACTION" />
+                <action android:name="com.android.cts.isolatedsplitapp.ACTION"/>
             </intent-filter>
         </receiver>
-        
-        <uses-library android:name="android.test.runner" />
+
+        <uses-library android:name="android.test.runner"/>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.isolatedsplitapp" />
+         android:targetPackage="com.android.cts.isolatedsplitapp"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
index d9ca271..7e2e1da 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
@@ -15,20 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.isolatedsplitapp.feature_a"
-        featureSplit="feature_a"
-        android:targetSandboxVersion="2">
+     package="com.android.cts.isolatedsplitapp.feature_a"
+     featureSplit="feature_a"
+     android:targetSandboxVersion="2">
 
     <application>
-        <activity android:name=".FeatureAActivity">
+        <activity android:name=".FeatureAActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".FeatureAReceiver">
+        <receiver android:name=".FeatureAReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.isolatedsplitapp.ACTION" />
+                <action android:name="com.android.cts.isolatedsplitapp.ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
index 8b4f16d..2b616cc 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
@@ -15,22 +15,24 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.isolatedsplitapp.feature_b"
-        featureSplit="feature_b"
-        android:targetSandboxVersion="2">
+     package="com.android.cts.isolatedsplitapp.feature_b"
+     featureSplit="feature_b"
+     android:targetSandboxVersion="2">
 
-    <uses-split android:name="feature_a" />
+    <uses-split android:name="feature_a"/>
 
     <application>
-        <activity android:name=".FeatureBActivity">
+        <activity android:name=".FeatureBActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".FeatureBReceiver">
+        <receiver android:name=".FeatureBReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.isolatedsplitapp.ACTION" />
+                <action android:name="com.android.cts.isolatedsplitapp.ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
index 012543b..3e65a43 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
@@ -15,20 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.isolatedsplitapp.feature_c"
-        featureSplit="feature_c"
-        android:targetSandboxVersion="2">
+     package="com.android.cts.isolatedsplitapp.feature_c"
+     featureSplit="feature_c"
+     android:targetSandboxVersion="2">
 
     <application>
-        <activity android:name=".FeatureCActivity">
+        <activity android:name=".FeatureCActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".FeatureCReceiver">
+        <receiver android:name=".FeatureCReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.isolatedsplitapp.ACTION" />
+                <action android:name="com.android.cts.isolatedsplitapp.ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/LocationPolicyApp/Android.bp b/hostsidetests/appsecurity/test-apps/LocationPolicyApp/Android.bp
index 83c6cb9..f1eb6bd 100644
--- a/hostsidetests/appsecurity/test-apps/LocationPolicyApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/LocationPolicyApp/Android.bp
@@ -16,8 +16,8 @@
     name: "CtsLocationPolicyApp",
     defaults: ["cts_defaults"],
     libs: [
-        "android.test.runner",
-        "android.test.base",
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
     ],
     static_libs: [
         "androidx.test.rules",
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest.xml
index cf69eaa..4b09ae5 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest.xml
@@ -13,28 +13,30 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.mediastorageapp">
+     package="com.android.cts.mediastorageapp">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.cts.mediastorageapp.StubActivity">
+        <activity android:name="com.android.cts.mediastorageapp.StubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.APP_GALLERY" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.APP_GALLERY"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity" />
+        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.mediastorageapp" />
+         android:targetPackage="com.android.cts.mediastorageapp"/>
 
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest28.xml b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest28.xml
index 93e4bc7..cefd4f8 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest28.xml
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest28.xml
@@ -13,32 +13,33 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.mediastorageapp28">
+     package="com.android.cts.mediastorageapp28">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.cts.mediastorageapp.StubActivity">
+        <activity android:name="com.android.cts.mediastorageapp.StubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.APP_GALLERY" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.APP_GALLERY"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity" />
+        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.mediastorageapp28" />
+         android:targetPackage="com.android.cts.mediastorageapp28"/>
 
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
-    <uses-sdk
-        android:minSdkVersion="28"
-        android:targetSdkVersion="28" />
+    <uses-sdk android:minSdkVersion="28"
+         android:targetSdkVersion="28"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest29.xml b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest29.xml
index a73ab0e..3412e0c 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest29.xml
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest29.xml
@@ -13,32 +13,32 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.mediastorageapp29">
+     package="com.android.cts.mediastorageapp29">
 
-    <application
-        android:requestLegacyExternalStorage="true">
-        <uses-library android:name="android.test.runner" />
+    <application android:requestLegacyExternalStorage="true">
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.cts.mediastorageapp.StubActivity">
+        <activity android:name="com.android.cts.mediastorageapp.StubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.APP_GALLERY" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.APP_GALLERY"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity" />
+        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.mediastorageapp29" />
+         android:targetPackage="com.android.cts.mediastorageapp29"/>
 
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
-    <uses-sdk
-        android:minSdkVersion="29"
-        android:targetSdkVersion="29" />
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
index 0fb7678..01e1c75 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
@@ -90,33 +90,8 @@
     }
 
     @Test
-    public void testSandboxed() throws Exception {
-        doSandboxed(true);
-    }
-
-    @Test
-    public void testNotSandboxed() throws Exception {
-        doSandboxed(false);
-    }
-
-    @Test
-    public void testStageFiles() throws Exception {
-        final File jpg = stageFile(TEST_JPG);
-        assertTrue(jpg.exists());
-        final File pdf = stageFile(TEST_PDF);
-        assertTrue(pdf.exists());
-    }
-
-    @Test
-    public void testClearFiles() throws Exception {
-        TEST_JPG.delete();
-        assertNull(MediaStore.scanFile(mContentResolver, TEST_JPG));
-        TEST_PDF.delete();
-        assertNull(MediaStore.scanFile(mContentResolver, TEST_PDF));
-    }
-
-    private void doSandboxed(boolean sandboxed) throws Exception {
-        assertEquals(!sandboxed, Environment.isExternalStorageLegacy());
+    public void testLegacy() throws Exception {
+        assertTrue(Environment.isExternalStorageLegacy());
 
         // We can always see mounted state
         assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
@@ -124,10 +99,8 @@
         // We might have top-level access
         final File probe = new File(Environment.getExternalStorageDirectory(),
                 "cts" + System.nanoTime());
-        if (!sandboxed) {
-            assertTrue(probe.createNewFile());
-            assertNotNull(Environment.getExternalStorageDirectory().list());
-        }
+        assertTrue(probe.createNewFile());
+        assertNotNull(Environment.getExternalStorageDirectory().list());
 
         // We always have our package directories
         final File probePackage = new File(mContext.getExternalFilesDir(null),
@@ -149,15 +122,24 @@
             }
         }
 
-        if (sandboxed) {
-            // If we're sandboxed, we should only see the image
-            assertTrue(seen.contains(ContentUris.parseId(jpgUri)));
-            assertFalse(seen.contains(ContentUris.parseId(pdfUri)));
-        } else {
-            // If we're not sandboxed, we should see both
-            assertTrue(seen.contains(ContentUris.parseId(jpgUri)));
-            assertTrue(seen.contains(ContentUris.parseId(pdfUri)));
-        }
+        assertTrue(seen.contains(ContentUris.parseId(jpgUri)));
+        assertTrue(seen.contains(ContentUris.parseId(pdfUri)));
+    }
+
+    @Test
+    public void testStageFiles() throws Exception {
+        final File jpg = stageFile(TEST_JPG);
+        assertTrue(jpg.exists());
+        final File pdf = stageFile(TEST_PDF);
+        assertTrue(pdf.exists());
+    }
+
+    @Test
+    public void testClearFiles() throws Exception {
+        TEST_JPG.delete();
+        assertNull(MediaStore.scanFile(mContentResolver, TEST_JPG));
+        TEST_PDF.delete();
+        assertNull(MediaStore.scanFile(mContentResolver, TEST_PDF));
     }
 
     @Test
@@ -211,6 +193,59 @@
             fail("Expected write access to be blocked");
         } catch (SecurityException | FileNotFoundException expected) {
         }
+
+        // Verify that we can't grant ourselves access
+        for (int flag : new int[] {
+                Intent.FLAG_GRANT_READ_URI_PERMISSION,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+        }) {
+            try {
+                mContext.grantUriPermission(mContext.getPackageName(), blue, flag);
+                fail("Expected granting to be blocked for flag 0x" + Integer.toHexString(flag));
+            } catch (SecurityException expected) {
+            }
+        }
+    }
+
+    /**
+     * Test prefix and non-prefix uri grant for all packages
+     */
+    @Test
+    public void testGrantUriPermission() {
+        final int flagGrantRead = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+        final int flagGrantWrite = Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+        final int flagGrantReadPrefix =
+                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+        final int flagGrantWritePrefix =
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+
+        for (Uri uri : new Uri[] {
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                MediaStore.Downloads.EXTERNAL_CONTENT_URI,
+                MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
+        }) {
+            // Non-prefix grant
+            checkGrantUriPermission(uri, flagGrantRead, true);
+            checkGrantUriPermission(uri, flagGrantWrite, true);
+
+            // Prefix grant
+            checkGrantUriPermission(uri, flagGrantReadPrefix, false);
+            checkGrantUriPermission(uri, flagGrantWritePrefix, false);
+        }
+    }
+
+    private void checkGrantUriPermission(Uri uri, int mode, boolean isGrantAllowed) {
+        if (isGrantAllowed) {
+            mContext.grantUriPermission(mContext.getPackageName(), uri, mode);
+        } else {
+            try {
+                mContext.grantUriPermission(mContext.getPackageName(), uri, mode);
+                fail("Expected granting to be blocked for flag 0x" + Integer.toHexString(mode));
+            } catch (SecurityException expected) {
+            }
+        }
     }
 
     @Test
@@ -538,7 +573,7 @@
     }
 
     static File stageFile(File file) throws Exception {
-        // Sometimes file creation fails due to slow permission update, try more times 
+        // Sometimes file creation fails due to slow permission update, try more times
         while(currentAttempt < MAX_NUMBER_OF_ATTEMPT) {
             try {
                 file.getParentFile().mkdirs();
@@ -549,7 +584,7 @@
                 // wait 500ms
                 Thread.sleep(500);
             }
-        } 
+        }
         throw new TimeoutException("File creation failed due to slow permission update");
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml
index c7550e0..366bc92 100644
--- a/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml
@@ -13,32 +13,32 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.cts.norestart"
-    android:targetSandboxVersion="2"
-    tools:ignore="MissingVersion" >
 
-    <application
-        tools:ignore="AllowBackup,MissingApplicationIcon" >
-        <activity
-            android:name=".NoRestartActivity"
-            android:launchMode="singleTop" >
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.cts.norestart"
+     android:targetSandboxVersion="2"
+     tools:ignore="MissingVersion">
+
+    <application tools:ignore="AllowBackup,MissingApplicationIcon">
+        <activity android:name=".NoRestartActivity"
+             android:launchMode="singleTop"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.norestart.START" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.norestart.START"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="https" />
-                <data android:host="cts.android.com" />
-                <data android:path="/norestart" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="https"/>
+                <data android:host="cts.android.com"/>
+                <data android:path="/norestart"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
             </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml
index b5f38c6..681264c 100644
--- a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml
@@ -13,124 +13,128 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.appsecurity.cts.orderedactivity"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.orderedactivity"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
         <!-- Activities used for queries -->
-        <activity android:name=".OrderActivity2">
-            <intent-filter
-                    android:order="2">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <activity android:name=".OrderActivity2"
+             android:exported="true">
+            <intent-filter android:order="2">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts/package" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts/package"/>
             </intent-filter>
         </activity>
-        <activity android:name=".OrderActivity1">
-            <intent-filter
-                    android:order="1">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <activity android:name=".OrderActivity1"
+             android:exported="true">
+            <intent-filter android:order="1">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:path="/cts/packageresolution" />
+                     android:host="www.google.com"
+                     android:path="/cts/packageresolution"/>
             </intent-filter>
         </activity>
-        <activity android:name=".OrderActivityDefault">
+        <activity android:name=".OrderActivityDefault"
+             android:exported="true">
             <intent-filter>
                 <!-- default order -->
-                <action android:name="android.cts.intent.action.ORDERED" />
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com" />
+                     android:host="www.google.com"/>
             </intent-filter>
         </activity>
-        <activity android:name=".OrderActivity3">
-            <intent-filter
-                    android:order="3">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <activity android:name=".OrderActivity3"
+             android:exported="true">
+            <intent-filter android:order="3">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts"/>
             </intent-filter>
         </activity>
 
         <!-- Services used for queries -->
-        <service android:name=".OrderServiceDefault">
+        <service android:name=".OrderServiceDefault"
+             android:exported="true">
             <intent-filter>
                 <!-- default order -->
-              <action android:name="android.cts.intent.action.ORDERED" />
+              <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com" />
+                     android:host="www.google.com"/>
             </intent-filter>
         </service>
-        <service android:name=".OrderService2">
-            <intent-filter
-                    android:order="2">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <service android:name=".OrderService2"
+             android:exported="true">
+            <intent-filter android:order="2">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts/package" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts/package"/>
             </intent-filter>
         </service>
-        <service android:name=".OrderService3">
-            <intent-filter
-                    android:order="3">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <service android:name=".OrderService3"
+             android:exported="true">
+            <intent-filter android:order="3">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts"/>
             </intent-filter>
         </service>
-        <service android:name=".OrderService1">
-            <intent-filter
-                    android:order="1">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <service android:name=".OrderService1"
+             android:exported="true">
+            <intent-filter android:order="1">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:path="/cts/packageresolution" />
+                     android:host="www.google.com"
+                     android:path="/cts/packageresolution"/>
             </intent-filter>
         </service>
 
         <!-- Broadcast receivers used for queries -->
-        <receiver android:name=".OrderReceiver3">
-            <intent-filter
-                    android:order="3">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <receiver android:name=".OrderReceiver3"
+             android:exported="true">
+            <intent-filter android:order="3">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".OrderReceiverDefault">
+        <receiver android:name=".OrderReceiverDefault"
+             android:exported="true">
             <intent-filter>
                 <!-- default order -->
-              <action android:name="android.cts.intent.action.ORDERED" />
+              <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com" />
+                     android:host="www.google.com"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".OrderReceiver1">
-            <intent-filter
-                    android:order="1">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <receiver android:name=".OrderReceiver1"
+             android:exported="true">
+            <intent-filter android:order="1">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:path="/cts/packageresolution" />
+                     android:host="www.google.com"
+                     android:path="/cts/packageresolution"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".OrderReceiver2">
-            <intent-filter
-                    android:order="2">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <receiver android:name=".OrderReceiver2"
+             android:exported="true">
+            <intent-filter android:order="2">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts/package" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts/package"/>
             </intent-filter>
         </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.appsecurity.cts.orderedactivity" />
+         android:targetPackage="android.appsecurity.cts.orderedactivity"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
index 6c4a335..f7628ab 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
@@ -15,50 +15,57 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.splitapp"
-    android:targetSandboxVersion="2">
+     package="com.android.cts.splitapp"
+     android:targetSandboxVersion="2">
 
-    <uses-sdk android:minSdkVersion="4" />
+    <uses-sdk android:minSdkVersion="4"/>
 
     <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <application android:label="SplitApp" android:multiArch="true">
-        <activity android:name=".MyActivity">
+    <application android:label="SplitApp"
+         android:multiArch="true">
+        <activity android:name=".MyActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
         </activity>
         <receiver android:name=".MyReceiver"
-                android:enabled="@bool/my_receiver_enabled">
+             android:enabled="@bool/my_receiver_enabled"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED" />
+                <action android:name="android.intent.action.DATE_CHANGED"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".LockedBootReceiver" android:exported="true" android:directBootAware="true">
+        <receiver android:name=".LockedBootReceiver"
+             android:exported="true"
+             android:directBootAware="true">
             <intent-filter>
-                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".BootReceiver" android:exported="true">
+        <receiver android:name=".BootReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
 
         <provider android:name=".RemoteQueryProvider"
-                  android:authorities="com.android.cts.splitapp"
-                  android:exported="true"
-                  android:directBootAware="true">
+             android:authorities="com.android.cts.splitapp"
+             android:exported="true"
+             android:directBootAware="true">
         </provider>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.splitapp" />
+         android:targetPackage="com.android.cts.splitapp"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
index be3adfc..c1ce39e 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
@@ -15,34 +15,40 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.splitapp"
-        split="feature">
+     package="com.android.cts.splitapp"
+     split="feature">
 
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="27" />
+    <uses-sdk android:minSdkVersion="4"
+         android:targetSdkVersion="27"/>
 
     <!-- New permission should be ignored -->
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <!-- New application flag should be ignored -->
     <application android:largeHeap="true">
-        <activity android:name=".FeatureActivity">
+        <activity android:name=".FeatureActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
         </activity>
         <receiver android:name=".FeatureReceiver"
-                android:enabled="@bool/feature_receiver_enabled">
+             android:enabled="@bool/feature_receiver_enabled"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED" />
+                <action android:name="android.intent.action.DATE_CHANGED"/>
             </intent-filter>
         </receiver>
-        <service android:name=".FeatureService">
+        <service android:name=".FeatureService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.splitapp.service" />
+                <action android:name="com.android.cts.splitapp.service"/>
             </intent-filter>
         </service>
-        <provider android:name=".FeatureProvider" android:authorities="com.android.cts.splitapp.provider" />
+        <provider android:name=".FeatureProvider"
+             android:authorities="com.android.cts.splitapp.provider"/>
     </application>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml
index 7ce1830..07f19e4 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml
@@ -15,35 +15,41 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.splitapp"
-        split="feature"
-        android:isSplitRequired="true">
+     package="com.android.cts.splitapp"
+     split="feature"
+     android:isSplitRequired="true">
 
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="27" />
+    <uses-sdk android:minSdkVersion="4"
+         android:targetSdkVersion="27"/>
 
     <!-- New permission should be ignored -->
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <!-- New application flag should be ignored -->
     <application android:largeHeap="true">
-        <activity android:name=".FeatureActivity">
+        <activity android:name=".FeatureActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
         </activity>
         <receiver android:name=".FeatureReceiver"
-                android:enabled="@bool/feature_receiver_enabled">
+             android:enabled="@bool/feature_receiver_enabled"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED" />
+                <action android:name="android.intent.action.DATE_CHANGED"/>
             </intent-filter>
         </receiver>
-        <service android:name=".FeatureService">
+        <service android:name=".FeatureService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.splitapp.service" />
+                <action android:name="com.android.cts.splitapp.service"/>
             </intent-filter>
         </service>
-        <provider android:name=".FeatureProvider" android:authorities="com.android.cts.splitapp.provider" />
+        <provider android:name=".FeatureProvider"
+             android:authorities="com.android.cts.splitapp.provider"/>
     </application>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
index a2c050c..b39e605 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
@@ -15,44 +15,51 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.splitapp"
-    android:targetSandboxVersion="2"
-    android:isSplitRequired="true" >
+     package="com.android.cts.splitapp"
+     android:targetSandboxVersion="2"
+     android:isSplitRequired="true">
 
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="27" />
+    <uses-sdk android:minSdkVersion="4"
+         android:targetSdkVersion="27"/>
 
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
 
     <application android:label="SplitApp">
-        <activity android:name=".MyActivity">
+        <activity android:name=".MyActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
         </activity>
         <receiver android:name=".MyReceiver"
-                android:enabled="@bool/my_receiver_enabled">
+             android:enabled="@bool/my_receiver_enabled"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED" />
+                <action android:name="android.intent.action.DATE_CHANGED"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".LockedBootReceiver" android:exported="true" android:directBootAware="true">
+        <receiver android:name=".LockedBootReceiver"
+             android:exported="true"
+             android:directBootAware="true">
             <intent-filter>
-                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".BootReceiver" android:exported="true">
+        <receiver android:name=".BootReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.splitapp" />
+         android:targetPackage="com.android.cts.splitapp"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml
index 727c6c5c..d61d7d4 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml
@@ -15,33 +15,36 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.splitapp"
-        android:targetSandboxVersion="2"
-        android:revisionCode="12">
+     package="com.android.cts.splitapp"
+     android:targetSandboxVersion="2"
+     android:revisionCode="12">
 
-    <uses-sdk android:targetSdkVersion="27" />
+    <uses-sdk android:targetSdkVersion="27"/>
 
-    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.CAMERA"/>
 
     <application android:label="SplitApp">
-        <activity android:name=".MyActivity">
+        <activity android:name=".MyActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
         </activity>
         <receiver android:name=".MyReceiver"
-                android:enabled="@bool/my_receiver_enabled">
+             android:enabled="@bool/my_receiver_enabled"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED" />
+                <action android:name="android.intent.action.DATE_CHANGED"/>
             </intent-filter>
         </receiver>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.splitapp" />
+         android:targetPackage="com.android.cts.splitapp"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
index 46ca3ae..6a04c09 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
@@ -145,9 +145,7 @@
         for (File f : files) {
             if (f.isDirectory()) {
                 if (excludeObb && f.getName().equalsIgnoreCase("obb")
-                        && f.getParentFile().getName().equalsIgnoreCase("Android")
-                        && !f.getParentFile().getParentFile().getParentFile().getName()
-                                .equalsIgnoreCase("sandbox")) {
+                        && f.getParentFile().getName().equalsIgnoreCase("Android")) {
                     Log.d(TAG, "Ignoring OBB directory " + f);
                 } else {
                     size += getSizeManual(f, excludeObb);
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion-shareduid.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion-shareduid.xml
index 642653f..1cbca72 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion-shareduid.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion-shareduid.xml
@@ -13,19 +13,20 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.appsecurity.cts.tinyapp_companion"
-        android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.tinyapp_companion"
+     android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion2-shareduid.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion2-shareduid.xml
index f7a639d..7377b00 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion2-shareduid.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion2-shareduid.xml
@@ -13,19 +13,20 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.appsecurity.cts.tinyapp_companion2"
-        android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.tinyapp_companion2"
+     android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-sandbox-v1.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-sandbox-v1.xml
index 8ca3557..e9e7c8c 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-sandbox-v1.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-sandbox-v1.xml
@@ -13,18 +13,19 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.appsecurity.cts.tinyapp"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="1">
+     package="android.appsecurity.cts.tinyapp"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="1">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-shareduid.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-shareduid.xml
index 2c4d3d9..6591656 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-shareduid.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-shareduid.xml
@@ -13,19 +13,20 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.appsecurity.cts.tinyapp"
-        android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.tinyapp"
+     android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-v2.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-v2.xml
index ef62aac..2c94a97 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-v2.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-v2.xml
@@ -14,18 +14,19 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.appsecurity.cts.tinyapp"
-        android:versionCode="20"
-        android:versionName="2.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.tinyapp"
+     android:versionCode="20"
+     android:versionName="2.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml
index 1ead3a2..39217ec 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml
@@ -13,18 +13,19 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.appsecurity.cts.tinyapp"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.tinyapp"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml b/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml
index 2f213ab..c60f51c 100644
--- a/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml
+++ b/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml
@@ -13,19 +13,21 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.atracetestapp"
-       android:targetSandboxVersion="2">
+     package="com.android.cts.atracetestapp"
+     android:targetSandboxVersion="2">
     <!--
-    A simple app with a tracing section to test that apps tracing signals are
-    emitted by atrace.
-    -->
+            A simple app with a tracing section to test that apps tracing signals are
+            emitted by atrace.
+            -->
     <application>
-        <activity android:name=".AtraceTestAppActivity">
+        <activity android:name=".AtraceTestAppActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <!-- Profileable to enable tracing -->
@@ -33,5 +35,5 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.atracetestapp" />
+         android:targetPackage="com.android.cts.atracetestapp"/>
 </manifest>
diff --git a/hostsidetests/backup/OWNERS b/hostsidetests/backup/OWNERS
index e0e5e22..9391645 100644
--- a/hostsidetests/backup/OWNERS
+++ b/hostsidetests/backup/OWNERS
@@ -1,9 +1,12 @@
-# Bug component: 41666
+# Bug component: 656484
 # Use this reviewer by default.
-br-framework-team+reviews@google.com
+br-platform-dev@google.com
 
-alsutton@google.com
-anniemeng@google.com
-brufino@google.com
-nathch@google.com
+# People who can approve changes for submission.
 rthakohov@google.com
+tobiast@google.com
+jstemmer@google.com
+aabhinav@google.com
+philippov@google.com
+niagra@google.com
+niamhfw@google.com
diff --git a/hostsidetests/backup/SharedPreferencesRestoreApp/AndroidManifest.xml b/hostsidetests/backup/SharedPreferencesRestoreApp/AndroidManifest.xml
index e2eb7c5..9939d4e 100644
--- a/hostsidetests/backup/SharedPreferencesRestoreApp/AndroidManifest.xml
+++ b/hostsidetests/backup/SharedPreferencesRestoreApp/AndroidManifest.xml
@@ -16,26 +16,25 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.backup.sharedprefrestoreapp">
+     package="android.cts.backup.sharedprefrestoreapp">
 
-    <application
-        android:backupAgent=".SharedPreferencesBackupAgent"
-        android:killAfterRestore="false" >
+    <application android:backupAgent=".SharedPreferencesBackupAgent"
+         android:killAfterRestore="false">
 
         <activity android:name=".SharedPrefsRestoreTestActivity"
-            android:launchMode="singleInstance">
+             android:launchMode="singleInstance"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.backup.cts.backuprestore.INIT" />
-                <action android:name="android.backup.cts.backuprestore.UPDATE" />
-                <action android:name="android.backup.cts.backuprestore.TEST" />
+                <action android:name="android.backup.cts.backuprestore.INIT"/>
+                <action android:name="android.backup.cts.backuprestore.UPDATE"/>
+                <action android:name="android.backup.cts.backuprestore.TEST"/>
             </intent-filter>
         </activity>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.cts.backup.sharedprefrestoreapp" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.cts.backup.sharedprefrestoreapp"/>
 
 </manifest>
diff --git a/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml b/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml
index 307b0e1..7a5cd6f 100644
--- a/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml
+++ b/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml
@@ -16,17 +16,17 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.backup.successnotificationapp">
+     package="android.cts.backup.successnotificationapp">
 
     <application>
-        <receiver android:name=".SuccessNotificationReceiver">
+        <receiver android:name=".SuccessNotificationReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.BACKUP_FINISHED" />
+                <action android:name="android.intent.action.BACKUP_FINISHED"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.cts.backup.successnotificationapp" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.cts.backup.successnotificationapp"/>
 </manifest>
diff --git a/hostsidetests/backup/SyncAdapterSettingsApp/AndroidManifest.xml b/hostsidetests/backup/SyncAdapterSettingsApp/AndroidManifest.xml
index a46ff41..dd96aa6 100644
--- a/hostsidetests/backup/SyncAdapterSettingsApp/AndroidManifest.xml
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/AndroidManifest.xml
@@ -16,35 +16,35 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.cts.backup.syncadaptersettingsapp">
+     package="android.cts.backup.syncadaptersettingsapp">
 
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
 
     <application android:label="BackupSyncAdapterSettings">
         <uses-library android:name="android.test.runner"/>
-        <service android:name=".SyncAdapterSettingsAuthenticator">
+        <service android:name=".SyncAdapterSettingsAuthenticator"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/authenticator"/>
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/authenticator"/>
         </service>
 
         <service android:name=".SyncAdapterSettingsService"
-                 android:exported="false">
+             android:exported="false">
             <intent-filter>
                 <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
             <meta-data android:name="android.content.SyncAdapter"
-                       android:resource="@xml/syncadapter"/>
+                 android:resource="@xml/syncadapter"/>
         </service>
 
         <provider android:name=".SyncAdapterSettingsProvider"
-                  android:authorities="android.cts.backup.syncadaptersettingsapp.provider"/>
+             android:authorities="android.cts.backup.syncadaptersettingsapp.provider"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.cts.backup.syncadaptersettingsapp"/>
+         android:targetPackage="android.cts.backup.syncadaptersettingsapp"/>
 </manifest>
diff --git a/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
index 89e2539..95d3b9a 100644
--- a/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
@@ -107,7 +107,7 @@
      *
      * <ol>
      *   <li>Install 3 test packages on the device
-     *   <li>Write dummy values to shared preferences for each package
+     *   <li>Write test values to shared preferences for each package
      *   <li>Backup each package (adb shell bmgr backupnow)
      *   <li>Clear shared preferences for each package
      *   <li>Run restore for the first {@code numPackagesToRestore}, verify only those are restored
@@ -120,7 +120,7 @@
         installPackage(getApkNameForTestApp(2));
         installPackage(getApkNameForTestApp(3));
 
-        // Write dummy value to shared preferences for all test packages.
+        // Write test values to shared preferences for all test packages.
         checkRestoreSessionDeviceTestForAllApps("testSaveValuesToSharedPrefs");
         checkRestoreSessionDeviceTestForAllApps("testCheckSharedPrefsExist");
 
diff --git a/hostsidetests/blobstore/OWNERS b/hostsidetests/blobstore/OWNERS
index 16b25bb..bf870975 100644
--- a/hostsidetests/blobstore/OWNERS
+++ b/hostsidetests/blobstore/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 95221
-include platform/frameworks/base:apex/blobstore/OWNERS
+include platform/frameworks/base:/apex/blobstore/OWNERS
diff --git a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataCleanupTest.java b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataCleanupTest.java
index bc248eb..b01be92 100644
--- a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataCleanupTest.java
+++ b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataCleanupTest.java
@@ -25,7 +25,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 
-import com.android.utils.blob.DummyBlobData;
+import com.android.utils.blob.FakeBlobData;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -42,7 +42,7 @@
 
     @Test
     public void testCreateSession() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                 .setRandomSeed(24)
                 .setFileName("test_blob_data")
                 .build();
@@ -79,7 +79,7 @@
 
     @Test
     public void testCommitBlob() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                 .setRandomSeed(24)
                 .setFileName("test_blob_data")
                 .setLabel("test_data_blob_label")
diff --git a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataPersistenceTest.java b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataPersistenceTest.java
index 5759e93..2efc523 100644
--- a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataPersistenceTest.java
+++ b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataPersistenceTest.java
@@ -24,7 +24,7 @@
 import android.os.ParcelFileDescriptor;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.utils.blob.DummyBlobData;
+import com.android.utils.blob.FakeBlobData;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -41,7 +41,7 @@
 
     @Test
     public void testCreateSession() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                 .setRandomSeed(22)
                 .setFileName("test_blob_data")
                 .build();
@@ -63,7 +63,7 @@
     @Test
     public void testOpenSessionAndWrite() throws Exception {
         final long sessionId = readSessionIdFromDisk();
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                 .setRandomSeed(22)
                 .setFileName("test_blob_data")
                 .build();
diff --git a/hostsidetests/car/Android.bp b/hostsidetests/car/Android.bp
index 91ef3a3..b09254e 100644
--- a/hostsidetests/car/Android.bp
+++ b/hostsidetests/car/Android.bp
@@ -29,4 +29,7 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":CtsCarApp",
+    ],
 }
diff --git a/hostsidetests/car/app/Android.bp b/hostsidetests/car/app/Android.bp
new file mode 100644
index 0000000..79df69b
--- /dev/null
+++ b/hostsidetests/car/app/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsCarApp",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/car/app/AndroidManifest.xml b/hostsidetests/car/app/AndroidManifest.xml
new file mode 100755
index 0000000..17b2685
--- /dev/null
+++ b/hostsidetests/car/app/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.car.cts.app">
+
+    <application>
+        <activity android:name=".SimpleActivity" android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/car/app/src/android/car/cts/app/SimpleActivity.java b/hostsidetests/car/app/src/android/car/cts/app/SimpleActivity.java
new file mode 100644
index 0000000..ce70a7c
--- /dev/null
+++ b/hostsidetests/car/app/src/android/car/cts/app/SimpleActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Very simple activity.
+ */
+public final class SimpleActivity extends Activity {
+
+    private static final String TAG = SimpleActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate()");
+        super.onCreate(savedInstanceState);
+    };
+}
diff --git a/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
new file mode 100644
index 0000000..6546110
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.car.cts.CarHostJUnit4TestCase.UserInfo;
+
+import com.android.compatibility.common.util.CommonTestUtils;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.ITestInformationReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.AssumptionViolatedException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Base class for all test cases.
+ */
+// NOTE: must be public because of @Rules
+public abstract class CarHostJUnit4TestCase extends BaseHostJUnit4Test {
+
+    private static final int DEFAULT_TIMEOUT_SEC = 20;
+
+    private static final Pattern CREATE_USER_OUTPUT_PATTERN = Pattern.compile("id=(\\d+)");
+
+    /**
+     * User pattern in the output of "cmd user list --all -v"
+     * TEXT id=<id> TEXT name=<name>, TEX flags=<flags> TEXT
+     * group 1: id group 2: name group 3: flags group 4: other state(like "(running)")
+     */
+    private static final Pattern USER_PATTERN = Pattern.compile(
+            ".*id=(\\d+).*name=([^\\s,]+).*flags=(\\S+)(.*)");
+
+    private static final int USER_PATTERN_GROUP_ID = 1;
+    private static final int USER_PATTERN_GROUP_NAME = 2;
+    private static final int USER_PATTERN_GROUP_FLAGS = 3;
+    private static final int USER_PATTERN_GROUP_OTHER_STATE = 4;
+
+    /**
+     * Permission pattern in the output of "dumpsys package PKG_NAME" =
+     * <permission_name>: TEXT granted=<true/false> TEXT flags=<flags> TEXT
+     * group 1: permission_name group 2: true/false group 3: flags
+     */
+    private static final String PERMISSION_REGEX =
+            "([\\w\\.^:]+).*granted=(true|false).*flags=(.*)";
+    private static final Pattern PERMISSION_PATTERN = Pattern.compile(PERMISSION_REGEX);
+
+    /**
+     * User's package permission pattern string format in the output of "dumpsys package PKG_NAME"
+    */
+    private static final String USER_PERMISSIONS_REGEX_FORMAT =
+            "User\\s%d(?:\\s*(:|/|overlay|runtime|gids).*\n)*((\\s*" + PERMISSION_REGEX + ")*)";
+    protected static final String APP_APK = "CtsCarApp.apk";
+    protected static final String APP_PKG = "android.car.cts.app";
+
+    @Rule
+    public final RequiredFeatureRule mHasAutomotiveRule = new RequiredFeatureRule(this,
+            "android.hardware.type.automotive");
+
+    private final HashSet<Integer> mUsersToBeRemoved = new HashSet<>();
+
+    private int mInitialUserId;
+    private Integer mInitialMaximumNumberOfUsers;
+
+    /**
+     * Saves multi-user state so it can be restored after the test.
+     */
+    @Before
+    public void saveUserState() throws Exception {
+        mInitialUserId = getCurrentUserId();
+    }
+
+    /**
+     * Restores multi-user state from before the test.
+     */
+    @After
+    public void restoreUsersState() throws Exception {
+        int currentUserId = getCurrentUserId();
+        CLog.d("restoreUsersState(): initial user: %d, current user: %d, created users: %s "
+                + "max number of users: %d",
+                mInitialUserId, currentUserId, mUsersToBeRemoved, mInitialMaximumNumberOfUsers);
+        if (currentUserId != mInitialUserId) {
+            CLog.i("Switching back from %d to %d", currentUserId, mInitialUserId);
+            switchUser(mInitialUserId);
+        }
+        if (!mUsersToBeRemoved.isEmpty()) {
+            CLog.i("Removing users %s", mUsersToBeRemoved);
+
+            for (int userId : mUsersToBeRemoved) {
+                removeUser(userId);
+            }
+        }
+        if (mInitialMaximumNumberOfUsers != null) {
+            CLog.i("Restoring max number of users to %d", mInitialMaximumNumberOfUsers);
+            setMaxNumberUsers(mInitialMaximumNumberOfUsers);
+        }
+    }
+
+    /**
+     * Makes sure the device supports multiple users, throwing {@link AssumptionViolatedException}
+     * if it doesn't.
+     */
+    protected void assumeSupportsMultipleUsers() throws Exception {
+        assumeTrue("device does not support multi-user",
+                getDevice().getMaxNumberOfUsersSupported() > 1);
+    }
+
+    /**
+     * Makes sure the device can add {@code numberOfUsers} new users, increasing limit if needed or
+     * failing if not possible.
+     */
+    protected void requiresExtraUsers(int numberOfUsers) throws Exception {
+        assumeSupportsMultipleUsers();
+
+        int maxNumber = getDevice().getMaxNumberOfUsersSupported();
+        int currentNumber = getDevice().listUsers().size();
+
+        if (currentNumber + numberOfUsers <= maxNumber) return;
+
+        if (!getDevice().isAdbRoot()) {
+            failCannotCreateUsers(numberOfUsers, currentNumber, maxNumber, /* isAdbRoot= */ false);
+        }
+
+        // Increase limit...
+        mInitialMaximumNumberOfUsers = maxNumber;
+        setMaxNumberUsers(maxNumber + numberOfUsers);
+
+        // ...and try again
+        maxNumber = getDevice().getMaxNumberOfUsersSupported();
+        if (currentNumber + numberOfUsers > maxNumber) {
+            failCannotCreateUsers(numberOfUsers, currentNumber, maxNumber, /* isAdbRoot= */ true);
+        }
+    }
+
+    private void failCannotCreateUsers(int numberOfUsers, int currentNumber, int maxNumber,
+            boolean isAdbRoot) {
+        String reason = isAdbRoot ? "failed to increase it"
+                : "cannot be increased without adb root";
+        String existingUsers = "";
+        try {
+            existingUsers = "Existing users: " + executeCommand("cmd user list --all -v");
+        } catch (Exception e) {
+            // ignore
+        }
+        fail("Cannot create " + numberOfUsers + " users: current number is " + currentNumber
+                + ", limit is " + maxNumber + " and could not be increased (" + reason + "). "
+                + existingUsers);
+    }
+
+    /**
+     * Executes the shell command and returns the output.
+     */
+    protected String executeCommand(String command, Object... args) throws Exception {
+        String fullCommand = String.format(command, args);
+        return getDevice().executeShellCommand(fullCommand);
+    }
+
+    /**
+     * Executes the shell command and parses output with {@code resultParser}.
+     */
+    protected <T> T executeAndParseCommand(Function<String, T> resultParser,
+            String command, Object... args) throws Exception {
+        String output = executeCommand(command, args);
+        return resultParser.apply(output);
+    }
+
+    /**
+     * Executes the shell command and parses the Matcher output with {@code resultParser}, failing
+     * with {@code matchNotFoundErrorMessage} if it didn't match the {@code regex}.
+     */
+    protected <T> T executeAndParseCommand(Pattern regex, String matchNotFoundErrorMessage,
+            Function<Matcher, T> resultParser,
+            String command, Object... args) throws Exception {
+        String output = executeCommand(command, args);
+        Matcher matcher = regex.matcher(output);
+        if (!matcher.find()) {
+            fail(matchNotFoundErrorMessage);
+        }
+        return resultParser.apply(matcher);
+    }
+
+    /**
+     * Executes the shell command and parses the Matcher output with {@code resultParser}.
+     */
+    protected <T> T executeAndParseCommand(Pattern regex, Function<Matcher, T> resultParser,
+            String command, Object... args) throws Exception {
+        String output = executeCommand(command, args);
+        return resultParser.apply(regex.matcher(output));
+    }
+
+    /**
+     * Executes the shell command that returns all users and returns {@code function} applied to
+     * them.
+     */
+    public <T> T onAllUsers(Function<List<UserInfo>, T> function) throws Exception {
+        ArrayList<UserInfo> allUsers = executeAndParseCommand(USER_PATTERN, (matcher) -> {
+            ArrayList<UserInfo> users = new ArrayList<>();
+            while (matcher.find()) {
+                users.add(new UserInfo(matcher));
+            }
+            return users;
+        }, "cmd user list --all -v");
+        return function.apply(allUsers);
+    }
+
+    /**
+     * Gets the info for the given user.
+     */
+    public UserInfo getUserInfo(int userId) throws Exception {
+        return onAllUsers((allUsers) -> allUsers.stream()
+                .filter((u) -> u.id == userId))
+                        .findFirst().get();
+    }
+
+    /**
+     * Sets the maximum number of users that can be created for this car.
+     *
+     * @throws IllegalStateException if adb is not running as root
+     */
+    protected void setMaxNumberUsers(int numUsers) throws Exception {
+        if (!getDevice().isAdbRoot()) {
+            throw new IllegalStateException("must be running adb root");
+        }
+        executeCommand("setprop fw.max_users %d", numUsers);
+    }
+
+    /**
+     * Gets the current user's id.
+     */
+    protected int getCurrentUserId() throws DeviceNotAvailableException {
+        return getDevice().getCurrentUser();
+    }
+
+    /**
+     * Creates a full user with car service shell command.
+     */
+    protected int createFullUser(String name) throws Exception {
+        return createUser(name, /* flags= */ 0, "android.os.usertype.full.SECONDARY");
+    }
+
+    /**
+     * Creates a full guest with car service shell command.
+     */
+    protected int createGuestUser(String name) throws Exception {
+        return createUser(name, /* flags= */ 0, "android.os.usertype.full.GUEST");
+    }
+
+    /**
+     * Creates a flexible user with car service shell command.
+     *
+     * <p><b>NOTE: </b>it uses User HAL flags, not core Android's.
+     */
+    protected int createUser(String name, int flags, String type) throws Exception {
+        int userId = executeAndParseCommand(CREATE_USER_OUTPUT_PATTERN,
+                "Could not create user with name " + name + ", flags " + flags + ", type" + type,
+                matcher -> Integer.parseInt(matcher.group(1)),
+                "cmd car_service create-user --flags %d --type %s %s",
+                flags, type, name);
+        markUserForRemovalAfterTest(userId);
+        return userId;
+    }
+
+    /**
+     * Marks a user to be removed at the end of the tests.
+     */
+    protected void markUserForRemovalAfterTest(int userId) {
+        mUsersToBeRemoved.add(userId);
+    }
+
+    /**
+     * Waits until the given user is initialized.
+     */
+    protected void waitForUserInitialized(int userId) throws Exception {
+        CommonTestUtils.waitUntil("timed out waiting for user " + userId + " initialization",
+                DEFAULT_TIMEOUT_SEC, () -> isUserInitialized(userId));
+    }
+
+    /**
+     * Asserts that the given user is initialized.
+     */
+    protected void assertUserInitialized(int userId) throws Exception {
+        assertWithMessage("User %s not initialized", userId).that(isUserInitialized(userId))
+                .isTrue();
+        CLog.v("User %d is initialized", userId);
+    }
+
+    /**
+     * Checks if the given user is initialized.
+     */
+    protected boolean isUserInitialized(int userId) throws Exception {
+        UserInfo userInfo = getUserInfo(userId);
+        CLog.v("isUserInitialized(%d): %s", userId, userInfo);
+        return userInfo.flags.contains("INITIALIZED");
+    }
+
+    /**
+     * Switches the current user.
+     */
+    protected void switchUser(int userId) throws Exception {
+        String output = executeCommand("cmd car_service switch-user %d", userId);
+        if (!output.contains("STATUS_SUCCESSFUL")) {
+            throw new IllegalStateException("Failed to switch to user " + userId + ": " + output);
+        }
+        waitUntilCurrentUser(userId);
+    }
+
+    /**
+     * Waits until the given user is the current foreground user.
+     */
+    protected void waitUntilCurrentUser(int userId) throws Exception {
+        CommonTestUtils.waitUntil("timed out (" + DEFAULT_TIMEOUT_SEC
+                + "s) waiting for current user to be " + userId
+                + " (it is " + getCurrentUserId() + ")",
+                DEFAULT_TIMEOUT_SEC,
+                () -> (getCurrentUserId() == userId));
+    }
+
+    /**
+     * Removes a user by user ID.
+     */
+    protected void removeUser(int userId) throws Exception {
+        executeCommand("cmd car_service remove-user %d", userId);
+    }
+
+    /**
+     * Checks if an app is installed for a given user.
+     */
+    protected boolean isAppInstalledForUser(String packageName, int userId)
+            throws DeviceNotAvailableException {
+        return getDevice().isPackageInstalled(packageName, Integer.toString(userId));
+    }
+
+    /**
+     * Fails the test if the app is installed for the given user.
+     */
+    protected void assertAppInstalledForUser(String packageName, int userId)
+            throws DeviceNotAvailableException {
+        assertWithMessage("%s should BE installed for user %s", packageName, userId).that(
+                isAppInstalledForUser(packageName, userId)).isTrue();
+    }
+
+    /**
+     * Fails the test if the app is NOT installed for the given user.
+     */
+    protected void assertAppNotInstalledForUser(String packageName, int userId)
+            throws DeviceNotAvailableException {
+        assertWithMessage("%s should NOT be installed for user %s", packageName, userId).that(
+                isAppInstalledForUser(packageName, userId)).isFalse();
+    }
+
+    /**
+     * Restarts the system server process.
+     *
+     * <p>Useful for cases where the test case changes system properties, as
+     * {@link ITestDevice#reboot()} would reset them.
+     */
+    protected void restartSystemServer() throws DeviceNotAvailableException {
+        final ITestDevice device = getDevice();
+        device.executeShellCommand("stop");
+        device.executeShellCommand("start");
+        device.waitForDeviceAvailable();
+    }
+
+    /**
+     * Get mapping of package and permissions granted for user.
+     *
+     * @param userId: the user to query permissions for
+     * @return Map<String, Map<String, Boolean>> where key is the package name and value is
+     * map of permissions
+     */
+    protected Map<String, Map<String, Boolean>> getPackagesAndPermissions(int userId)
+            throws Exception {
+        Map<String, Map<String, Boolean>> pkgMap = new HashMap<>();
+        Set<String> installedPackages = getDevice().getInstalledPackageNames();
+        CLog.v("Device has %d packages: %s", installedPackages.size(), installedPackages);
+        for (String pkg : installedPackages) {
+            if (isAppInstalledForUser(pkg, userId)) {
+                Map<String, Boolean> permMap = getPackagePermissionsForUser(pkg, userId);
+                pkgMap.put(pkg, permMap);
+                CLog.v("User %d package %s has %d permissions", userId, pkg, permMap.size());
+            }
+        }
+        return pkgMap;
+    }
+
+    // TODO(b/167454361): use dumpsys as proto
+    /**
+     * Gets package's permissions and whether it's granted for user.
+     *
+     * @param pkg: name of package to check for permission
+     * @param userId: the user to query permissions for
+     * @return Map<String, Boolean> where key is the permission name and value is whether granted
+     * @throws DeviceNotAvailableException
+     */
+    protected Map<String, Boolean> getPackagePermissionsForUser(String pkg, int userId)
+            throws Exception {
+        String command = String.format("dumpsys package %s", pkg);
+        String output = getDevice().executeShellCommand(command);
+        Map<String, Boolean> permissionMap = new HashMap<>();
+
+        String userPermissionsRegex = String.format(USER_PERMISSIONS_REGEX_FORMAT, userId);
+        Pattern userPermissionsPattern = Pattern.compile(userPermissionsRegex);
+        Matcher userPermissionsMatcher = userPermissionsPattern.matcher(output);
+
+        if (userPermissionsMatcher.find()) {
+            Matcher permissionMatcher = PERMISSION_PATTERN.matcher(userPermissionsMatcher.group(0));
+            while (permissionMatcher.find()) {
+                permissionMap.put(permissionMatcher.group(1),
+                        permissionMatcher.group(2).equals("true"));
+            }
+            CLog.d("No more permission found for package %s user %d.", pkg, userId);
+            return permissionMap;
+        }
+
+        CLog.d("No matcher permissions found for package %s user %d.", pkg, userId);
+        return permissionMap;
+    }
+
+    /**
+     * Sleeps for the given amount of milliseconds.
+     */
+    protected void sleep(long ms) throws InterruptedException {
+        CLog.v("Sleeping for %dms", ms);
+        Thread.sleep(ms);
+        CLog.v("Woke up; Little Susie woke up!");
+    }
+
+    // TODO(b/169341308): move to common infra code
+    private static final class RequiredFeatureRule implements TestRule {
+
+        private final ITestInformationReceiver mReceiver;
+        private final String mFeature;
+
+        RequiredFeatureRule(ITestInformationReceiver receiver, String feature) {
+            mReceiver = receiver;
+            mFeature = feature;
+        }
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+
+                @Override
+                public void evaluate() throws Throwable {
+                    boolean hasFeature = false;
+                    try {
+                        hasFeature = mReceiver.getTestInformation().getDevice()
+                                .hasFeature(mFeature);
+                    } catch (DeviceNotAvailableException e) {
+                        CLog.e("Could not check if device has feature %s: %e", mFeature, e);
+                        return;
+                    }
+
+                    if (!hasFeature) {
+                        CLog.d("skipping %s#%s"
+                                + " because device does not have feature '%s'",
+                                description.getClassName(), description.getMethodName(), mFeature);
+                        throw new AssumptionViolatedException("Device does not have feature '"
+                                + mFeature + "'");
+                    }
+                    base.evaluate();
+                }
+            };
+        }
+
+        @Override
+        public String toString() {
+            return "RequiredFeatureRule[" + mFeature + "]";
+        }
+    }
+
+    /**
+     * Represents a user as returned by {@code cmd user list -v}.
+     */
+    public static final class UserInfo {
+        public final int id;
+        public final String flags;
+        public final String name;
+        public final String otherState;
+
+        private UserInfo(Matcher matcher) {
+            id = Integer.parseInt(matcher.group(USER_PATTERN_GROUP_ID));
+            flags = matcher.group(USER_PATTERN_GROUP_FLAGS);
+            name = matcher.group(USER_PATTERN_GROUP_NAME);
+            otherState = matcher.group(USER_PATTERN_GROUP_OTHER_STATE);
+        }
+
+        @Override
+        public String toString() {
+            return "[UserInfo: id=" + id + ", flags=" + flags + ", name=" + name
+                    + ", otherState=" + otherState + "]";
+        }
+    }
+}
diff --git a/hostsidetests/car/src/android/car/cts/OptionalFeatureHostTest.java b/hostsidetests/car/src/android/car/cts/OptionalFeatureHostTest.java
index 55425d4..836467a 100644
--- a/hostsidetests/car/src/android/car/cts/OptionalFeatureHostTest.java
+++ b/hostsidetests/car/src/android/car/cts/OptionalFeatureHostTest.java
@@ -20,12 +20,9 @@
 
 import static org.hamcrest.CoreMatchers.endsWith;
 import static org.junit.Assume.assumeThat;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -39,9 +36,7 @@
  * Check Optional Feature related car configs.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class OptionalFeatureHostTest extends BaseHostJUnit4Test {
-
-    private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+public class OptionalFeatureHostTest extends CarHostJUnit4TestCase {
 
     private static final String[] MANDATORY_FEATURES = {
             "android.car.input",
@@ -55,7 +50,6 @@
             "car_occupant_zone_service",
             "car_user_service",
             "car_watchdog",
-            "configuration",
             "drivingstate",
             "hvac",
             "info",
@@ -68,11 +62,6 @@
             "vendor_extension"
     };
 
-    @Before
-    public void setUp() throws Exception {
-        assumeTrue(hasDeviceFeature(FEATURE_AUTOMOTIVE));
-    }
-
     /**
      * Partners can use the same system image for multiple product configs with variation in
      * optional feature support. But CTS should run in a config where VHAL
diff --git a/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java b/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java
new file mode 100644
index 0000000..0c10908
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts;
+
+import static com.android.tradefed.device.NativeDevice.INVALID_USER_ID;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.AssumptionViolatedException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Tests for pre-created users.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class PreCreateUsersHostTest extends CarHostJUnit4TestCase {
+
+    private static int sNumberCreateadUsers;
+
+    /**
+     * Uninstalls the test app.
+     */
+    @Before
+    @After
+    public void uninstallTestApp() throws Exception {
+        assumeSupportsMultipleUsers();
+        getDevice().uninstallPackage(APP_PKG);
+    }
+
+    /**
+     * Makes sure an app installed for a regular user is not visible to a pre-created user.
+     */
+    @Presubmit
+    @Test
+    public void testAppsAreNotInstalledOnPreCreatedUser() throws Exception {
+        appsAreNotInstalledOnPreCreatedUserTest(/* isGuest= */ false, /* afterReboot= */ false);
+    }
+
+    /**
+     * Same as {@link #testAppsAreNotInstalledOnPreCreatedUser()}, but for a guest user.
+     */
+    @Presubmit
+    @Test
+    public void testAppsAreNotInstalledOnPreCreatedGuest() throws Exception {
+        appsAreNotInstalledOnPreCreatedUserTest(/* isGuest= */ true, /* afterReboot= */ false);
+    }
+
+    /**
+     * Makes sure an app installed for a regular user is not visible to a pre-created user, even
+     * after the system restarts
+     */
+    @Presubmit
+    @Test
+    public void testAppsAreNotInstalledOnPreCreatedUserAfterReboot() throws Exception {
+        appsAreNotInstalledOnPreCreatedUserTest(/* isGuest= */ false, /* afterReboot= */ true);
+    }
+
+    /**
+     * Same as {@link #testAppsAreNotInstalledOnPreCreatedUserAfterReboot()}, but for a guest
+     * user.
+     */
+    @Presubmit
+    @Test
+    public void testAppsAreNotInstalledOnPreCreatedGuestAfterReboot() throws Exception {
+        appsAreNotInstalledOnPreCreatedUserTest(/* isGuest= */ true, /* afterReboot= */ true);
+    }
+
+    private void appsAreNotInstalledOnPreCreatedUserTest(boolean isGuest,
+            boolean afterReboot) throws Exception {
+        deletePreCreatedUsers();
+        requiresExtraUsers(1);
+
+        int initialUserId = getCurrentUserId();
+        int preCreatedUserId = preCreateUser(isGuest);
+
+        installPackageAsUser(APP_APK, /* grantPermission= */ false, initialUserId);
+
+        assertAppInstalledForUser(APP_PKG, initialUserId);
+        assertAppNotInstalledForUser(APP_PKG, preCreatedUserId);
+
+        if (afterReboot) {
+            restartSystemWithOnePreCreatedUserOrGuest(isGuest);
+
+            // Checks again
+            assertAppInstalledForUser(APP_PKG, initialUserId);
+            assertAppNotInstalledForUser(APP_PKG, preCreatedUserId);
+        }
+        convertPreCreatedUser(isGuest, preCreatedUserId);
+        assertAppNotInstalledForUser(APP_PKG, preCreatedUserId);
+    }
+
+    /**
+     * Verifies a pre-created user have same packages as non-precreated users.
+     */
+    @Presubmit
+    @Test
+    public void testAppPermissionsPreCreatedUserPackages() throws Exception {
+        appPermissionsPreCreatedUserPackagesTest(/* isGuest= */ false, /* afterReboot= */ false);
+    }
+
+    /**
+     * Verifies a pre-created guest have same packages as non-precreated users.
+     */
+    @Presubmit
+    @Test
+    public void testAppPermissionsPreCreatedGuestPackages() throws Exception {
+        appPermissionsPreCreatedUserPackagesTest(/* isGuest= */ true, /* afterReboot= */ false);
+    }
+
+    /**
+     * Verifies a pre-created user have same packages as non-precreated users.
+     */
+    @Presubmit
+    @Test
+    public void testAppPermissionsPreCreatedUserPackagesAfterReboot() throws Exception {
+        appPermissionsPreCreatedUserPackagesTest(/* isGuest= */ false, /* afterReboot= */ true);
+    }
+
+    /**
+     * Verifies a pre-created guest have same packages as non-precreated users.
+     */
+    @Presubmit
+    @Test
+    public void testAppPermissionsPreCreatedGuestPackagesAfterReboot() throws Exception {
+        appPermissionsPreCreatedUserPackagesTest(/* isGuest= */ true, /* afterReboot= */ true);
+    }
+
+    private void appPermissionsPreCreatedUserPackagesTest(boolean isGuest, boolean afterReboot)
+            throws Exception {
+        deletePreCreatedUsers();
+        requiresExtraUsers(2);
+
+        // Create a normal reference user
+        int referenceUserId = isGuest
+                ? createGuestUser("PreCreatedUsersTest_Reference_Guest")
+                : createFullUser("PreCreatedUsersTest_Reference_User");
+        Map<String, Map<String, Boolean>> pkgMapRef = getPackagesAndPermissions(referenceUserId);
+
+        int initialUserId = getCurrentUserId();
+        int preCreatedUserId = preCreateUser(isGuest);
+
+        if (afterReboot) {
+            restartSystemWithOnePreCreatedUserOrGuest(isGuest);
+        }
+
+        convertPreCreatedUser(isGuest, preCreatedUserId);
+
+        // Checks the packages and permissions match
+        Map<String, Map<String, Boolean>> pkgMapConverted = getPackagesAndPermissions(
+                preCreatedUserId);
+
+        comparePackages("converted user", pkgMapRef, pkgMapConverted);
+    }
+
+    private void comparePackages(String name, Map<String, Map<String, Boolean>> ref,
+            Map<String, Map<String, Boolean>> actual) {
+        // TODO: figure out a if Truth or other tool provides an easier way to compare maps
+        List<String> errors = new ArrayList<>();
+        addError(errors,
+                () -> assertWithMessage("packages mismatch for %s", name).that(actual.keySet())
+                        .containsAllIn(ref.keySet()));
+        Set<String> pkgsOnlyOnActual = actual.keySet();
+        for (String refPackage: ref.keySet()) {
+            if (pkgsOnlyOnActual.contains(refPackage)) {
+                Map<String, Boolean> refPermissions = ref.get(refPackage);
+                Map<String, Boolean> actualPermissions = actual.get(refPackage);
+                pkgsOnlyOnActual.remove(refPackage);
+
+                addError(errors, () ->
+                        assertWithMessage("permissions list mismatch for %s on %s",
+                                refPackage, name)
+                                .that(actualPermissions.keySet())
+                                .isEqualTo(refPermissions.keySet()));
+
+                addError(errors, () ->
+                        assertWithMessage("permissions state mismatch for %s on %s",
+                                refPackage, name)
+                                .that(actualPermissions).isEqualTo(refPermissions));
+            } else {
+                errors.add("Package " + refPackage + " not found on " + actual);
+            }
+        }
+        // TODO(b/167454361): remove this 'if' once it uses dumpsys proto and it's not flaky
+        if (!errors.isEmpty()) {
+            throw new AssumptionViolatedException("Found " + errors.size() + ": " + errors);
+        }
+
+        assertWithMessage("found %s error", errors.size()).that(errors).isEmpty();
+    }
+
+    private void addError(List<String> error, Runnable r) {
+        try {
+            r.run();
+        } catch (Throwable t) {
+            error.add(t.getMessage());
+        }
+    }
+
+    private void assertHasPreCreatedUser(int userId) throws Exception {
+        List<Integer> existingIds = getPreCreatedUsers();
+        CLog.d("asserHasPreCreatedUser(%d): pool=%s", userId, existingIds);
+        assertWithMessage("pre-created user not found").that(existingIds).contains(userId);
+    }
+
+    private List<Integer> getPreCreatedUsers() throws Exception {
+        return onAllUsers((allUsers) -> allUsers.stream()
+                    .filter((u) -> u.otherState.contains("(pre-created)"))
+                    .map((u) -> u.id).collect(Collectors.toList()));
+    }
+
+    private int preCreateUser(boolean isGuest) throws Exception {
+        return executeAndParseCommand((output) -> {
+            int userId = INVALID_USER_ID;
+            if (output.startsWith("Success")) {
+                try {
+                    userId = Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+                    CLog.i("Pre-created user with id %d; waiting until it's initialized", userId);
+                    markUserForRemovalAfterTest(userId);
+                    waitForUserInitialized(userId);
+                    assertHasPreCreatedUser(userId);
+                    waitUntilUserDataIsPersisted(userId);
+                } catch (Exception e) {
+                    CLog.e("Exception pre-creating %s: %s", (isGuest ? "guest" : "user"), e);
+                }
+            }
+            if (userId == INVALID_USER_ID) {
+                throw new IllegalStateException("failed to pre-create user");
+            }
+            return userId;
+        }, "pm create-user --pre-create-only%s", (isGuest ? " --guest" : ""));
+    }
+
+    // TODO(b/169588446): remove method and callers once it's not needed anymore
+    private void waitUntilUserDataIsPersisted(int userId) throws InterruptedException {
+        int napTimeSec = 10;
+        CLog.i("Sleeping %ds to make sure user data for user %d is persisted", napTimeSec, userId);
+        sleep(napTimeSec * 1_000);
+    }
+
+    private void deletePreCreatedUsers() throws Exception {
+        List<Integer> userIds = getPreCreatedUsers();
+        for (int userId : userIds) {
+            getDevice().removeUser(userId);
+        }
+    }
+
+    private void setPreCreatedUsersProperties(int value) throws DeviceNotAvailableException {
+        getDevice().setProperty("android.car.number_pre_created_users", Integer.toString(value));
+    }
+
+    private void setPreCreatedGuestsProperties(int value) throws DeviceNotAvailableException {
+        getDevice().setProperty("android.car.number_pre_created_guests", Integer.toString(value));
+    }
+
+    private void convertPreCreatedUser(boolean isGuest, int expectedId) throws Exception {
+        assertHasPreCreatedUser(expectedId);
+        String type = isGuest ? "guest" : "user";
+        int suffix = ++sNumberCreateadUsers;
+        int newUserId = isGuest
+                ? createGuestUser("PreCreatedUsersTest_ConvertedGuest_" + suffix)
+                : createFullUser("PreCreatedUsersTest_ConvertedUser_" + suffix);
+        if (newUserId == expectedId) {
+            CLog.i("Created new %s from pre-created %s with id %d", type, type, newUserId);
+            return;
+        }
+        fail("Created new " + type + " with id " + newUserId + ", which doesn't match pre-created "
+                + "id " + expectedId);
+    }
+
+    private void restartSystemWithOnePreCreatedUserOrGuest(boolean isGuest) throws Exception {
+        List<Integer> ids = getPreCreatedUsers();
+        CLog.d("Pre-created users before boot: %s", ids);
+        assertWithMessage("Should have just 1 pre-created user before boot").that(ids).hasSize(1);
+        assertUserInitialized(ids.get(0));
+
+        // CarUserService creates / remove pre-created users on boot to keep the pool constant,
+        // based on system properties. We need to tune then so the pre-created users set by this
+        // test are not changed when the system restarts.
+        if (isGuest) {
+            setPreCreatedGuestsProperties(1);
+            setPreCreatedUsersProperties(0);
+        } else {
+            setPreCreatedUsersProperties(1);
+            setPreCreatedGuestsProperties(0);
+        }
+
+        // Restart the system to make sure PackageManager preserves the installed bit
+        restartSystemServer();
+    }
+}
diff --git a/hostsidetests/car/src/android/car/cts/UiModeHostTest.java b/hostsidetests/car/src/android/car/cts/UiModeHostTest.java
new file mode 100644
index 0000000..f0158f7
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/UiModeHostTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+/**
+ * Check car config consistency across day night mode switching.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class UiModeHostTest extends CarHostJUnit4TestCase {
+
+    private static final Pattern NIGHT_MODE_REGEX = Pattern.compile("Night mode: (yes|no)");
+
+    /**
+     * Test day/night mode consistency across user switching. Day/night mode config should be
+     * persistent across user switching.
+     */
+    @Test
+    public void testUserSwitchingConfigConsistency() throws Exception {
+        requiresExtraUsers(1);
+
+        int originalUserId = getCurrentUserId();
+        int newUserId = createFullUser("UiModeHostTest");
+
+        // start current user in day mode
+        setDayMode();
+        assertThat(isNightMode()).isFalse();
+
+        // set to night mode
+        setNightMode();
+        assertThat(isNightMode()).isTrue();
+
+        // switch to new user and verify night mode
+        switchUser(newUserId);
+        assertThat(isNightMode()).isTrue();
+
+        // set to day mode
+        setDayMode();
+        assertThat(isNightMode()).isFalse();
+
+        // switch bach to initial user and verify day mode
+        switchUser(originalUserId);
+        assertThat(isNightMode()).isFalse();
+    }
+
+    /**
+     * Sets the UI mode to day mode.
+     */
+    protected void setDayMode() throws Exception {
+        executeCommand("cmd car_service day-night-mode day");
+    }
+
+    /**
+     * Sets the UI mode to night mode.
+     */
+    protected void setNightMode() throws Exception {
+        executeCommand("cmd car_service day-night-mode night");
+    }
+
+    /**
+     * Returns true if the current UI mode is night mode, false otherwise.
+     */
+    protected boolean isNightMode() throws Exception {
+        return executeAndParseCommand(NIGHT_MODE_REGEX,
+                "get night mode status failed",
+                matcher -> matcher.group(1).equals("yes"),
+                "cmd uimode night");
+    }
+}
diff --git a/hostsidetests/classloaders/splits/apps/AndroidManifest.xml b/hostsidetests/classloaders/splits/apps/AndroidManifest.xml
index d2499fb..d693c21 100644
--- a/hostsidetests/classloaders/splits/apps/AndroidManifest.xml
+++ b/hostsidetests/classloaders/splits/apps/AndroidManifest.xml
@@ -15,29 +15,31 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.classloadersplitapp"
-    android:isolatedSplits="true"
-    android:targetSandboxVersion="2">
+     package="com.android.cts.classloadersplitapp"
+     android:isolatedSplits="true"
+     android:targetSandboxVersion="2">
 
     <application android:label="ClassloaderSplitApp"
-                 android:classLoader="dalvik.system.PathClassLoader">
+         android:classLoader="dalvik.system.PathClassLoader">
 
-        <activity android:name=".BaseActivity">
+        <activity android:name=".BaseActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
           </activity>
-          <receiver android:name=".BaseReceiver">
+          <receiver android:name=".BaseReceiver"
+               android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.classloadersplitapp.ACTION" />
+                <action android:name="com.android.cts.classloadersplitapp.ACTION"/>
             </intent-filter>
           </receiver>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.classloadersplitapp" />
+         android:targetPackage="com.android.cts.classloadersplitapp"/>
 
 </manifest>
diff --git a/hostsidetests/classloaders/splits/apps/feature_a/AndroidManifest.xml b/hostsidetests/classloaders/splits/apps/feature_a/AndroidManifest.xml
index 96807d6..6d801e9 100644
--- a/hostsidetests/classloaders/splits/apps/feature_a/AndroidManifest.xml
+++ b/hostsidetests/classloaders/splits/apps/feature_a/AndroidManifest.xml
@@ -15,20 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.classloadersplitapp"
-        featureSplit="feature_a"
-        android:targetSandboxVersion="2">
+     package="com.android.cts.classloadersplitapp"
+     featureSplit="feature_a"
+     android:targetSandboxVersion="2">
 
     <application android:classLoader="dalvik.system.DelegateLastClassLoader">
-        <activity android:name=".feature_a.FeatureAActivity">
+        <activity android:name=".feature_a.FeatureAActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".feature_a.FeatureAReceiver">
+        <receiver android:name=".feature_a.FeatureAReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.classloadersplitapp.ACTION" />
+                <action android:name="com.android.cts.classloadersplitapp.ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/classloaders/splits/apps/feature_b/AndroidManifest.xml b/hostsidetests/classloaders/splits/apps/feature_b/AndroidManifest.xml
index fa975ad..3cde8aee 100644
--- a/hostsidetests/classloaders/splits/apps/feature_b/AndroidManifest.xml
+++ b/hostsidetests/classloaders/splits/apps/feature_b/AndroidManifest.xml
@@ -15,22 +15,24 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.classloadersplitapp"
-        featureSplit="feature_b"
-        android:targetSandboxVersion="2">
+     package="com.android.cts.classloadersplitapp"
+     featureSplit="feature_b"
+     android:targetSandboxVersion="2">
 
-    <uses-split android:name="feature_a" />
+    <uses-split android:name="feature_a"/>
 
     <application>
-        <activity android:name=".feature_b.FeatureBActivity">
+        <activity android:name=".feature_b.FeatureBActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".feature_b.FeatureBReceiver">
+        <receiver android:name=".feature_b.FeatureBReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.classloadersplitapp.ACTION" />
+                <action android:name="com.android.cts.classloadersplitapp.ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/compilation/app/AndroidManifest.xml b/hostsidetests/compilation/app/AndroidManifest.xml
index cca9341..a27edfc 100755
--- a/hostsidetests/compilation/app/AndroidManifest.xml
+++ b/hostsidetests/compilation/app/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.compilation.cts">
-    <uses-sdk android:minSdkVersion="23" />
+     package="android.compilation.cts">
+    <uses-sdk android:minSdkVersion="23"/>
     <application>
-        <activity android:name=".CompilationTargetActivity" >
+        <activity android:name=".CompilationTargetActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java b/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
index 0e473f2..3c7e2c7 100644
--- a/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
+++ b/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
@@ -18,7 +18,6 @@
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsNotLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
@@ -414,7 +413,6 @@
     @Test
     public void testBindServiceAsUser_sameProfileGroup_reportsMetric()
             throws Exception {
-        assumeTrue(isStatsdEnabled(getDevice()));
         assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */ true);
@@ -455,7 +453,6 @@
     @Test
     public void testBindServiceAsUser_differentProfileGroup_doesNotReportMetric()
             throws Exception {
-        assumeTrue(isStatsdEnabled(getDevice()));
         int userInDifferentProfileGroup = createUser();
         getDevice().startUser(userInDifferentProfileGroup, /* waitFlag= */ true);
         mTestArgs.put("testUser", Integer.toString(userInDifferentProfileGroup));
@@ -492,8 +489,6 @@
     @Test
     public void testBindServiceAsUser_sameUser_doesNotReportMetric()
             throws Exception {
-        assumeTrue(isStatsdEnabled(getDevice()));
-
         mTestArgs.put("testUser", Integer.toString(mParentUserId));
 
         assertMetricsNotLogged(getDevice(), () -> {
diff --git a/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/AndroidManifest.xml b/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/AndroidManifest.xml
index ed2d8dc..952829f 100644
--- a/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/AndroidManifest.xml
+++ b/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/AndroidManifest.xml
@@ -15,30 +15,27 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.content.sync.cts">
+     package="android.content.sync.cts">
 
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <service
-            android:name=".StubAuthenticator">
+        <uses-library android:name="android.test.runner"/>
+        <service android:name=".StubAuthenticator"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/authenticator" />
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/authenticator"/>
         </service>
 
-        <provider
-            android:name=".StubProvider"
-            android:authorities="android.content.sync.cts.authority">
+        <provider android:name=".StubProvider"
+             android:authorities="android.content.sync.cts.authority">
         </provider>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.content.sync.cts" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.content.sync.cts"/>
 </manifest>
diff --git a/hostsidetests/cpptools/TEST_MAPPING b/hostsidetests/cpptools/TEST_MAPPING
new file mode 100644
index 0000000..3707f3b
--- /dev/null
+++ b/hostsidetests/cpptools/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsCppToolsTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/cpptools/test-apps/BasicApp/AndroidManifest.xml b/hostsidetests/cpptools/test-apps/BasicApp/AndroidManifest.xml
index 6d4681e..aae67b7 100755
--- a/hostsidetests/cpptools/test-apps/BasicApp/AndroidManifest.xml
+++ b/hostsidetests/cpptools/test-apps/BasicApp/AndroidManifest.xml
@@ -16,17 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cpptools.app">
+     package="android.cpptools.app">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <application android:debuggable="true">
-        <activity android:name=".CppToolsDeviceActivity" >
+        <activity android:name=".CppToolsDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/cpptools/test-apps/DomainSocketApp/AndroidManifest.xml b/hostsidetests/cpptools/test-apps/DomainSocketApp/AndroidManifest.xml
index b37f769..be602e9 100644
--- a/hostsidetests/cpptools/test-apps/DomainSocketApp/AndroidManifest.xml
+++ b/hostsidetests/cpptools/test-apps/DomainSocketApp/AndroidManifest.xml
@@ -16,14 +16,15 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.domainsocketapp">
+     package="com.android.cts.domainsocketapp">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <application android:debuggable="true">
-        <activity android:name=".DomainSocketActivity" >
+        <activity android:name=".DomainSocketActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/devicepolicy/TEST_MAPPING b/hostsidetests/devicepolicy/TEST_MAPPING
index 3d86cf3..d68a863 100644
--- a/hostsidetests/devicepolicy/TEST_MAPPING
+++ b/hostsidetests/devicepolicy/TEST_MAPPING
@@ -1,13 +1,26 @@
 {
-  "presubmit-devicepolicy": [
+  "presubmit": [
     {
       "name": "CtsDevicePolicyManagerTestCases",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+          "include-annotation": "com.android.cts.devicepolicy.annotations.PermissionsTest"
         },
         {
-          "exclude-annotation": "android.platform.test.annotations.LargeTest"
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        }
+      ]
+    }
+  ],
+  "presubmit": [
+    {
+      "name": "CtsDevicePolicyManagerTestCases",
+      "options": [
+        {
+          "include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
         }
       ]
     }
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/NonTestOnlyOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AccountCheck/NonTestOnlyOwner/AndroidManifest.xml
index 6b130b5..4f8f41a 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/NonTestOnlyOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AccountCheck/NonTestOnlyOwner/AndroidManifest.xml
@@ -16,22 +16,22 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.accountcheck.nontestonly"
-    android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
+     package="com.android.cts.devicepolicy.accountcheck.nontestonly"
+     android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
 
     <application android:testOnly="false">
-        <receiver
-            android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
     <!--
-      Don't need instrumentation. All the three device side apps have the same UID, so we're able
-      to run all tests from the Auth package.
-    -->
+              Don't need instrumentation. All the three device side apps have the same UID, so we're able
+              to run all tests from the Auth package.
+            -->
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwner/AndroidManifest.xml
index a9673e9..ba829b4 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwner/AndroidManifest.xml
@@ -16,22 +16,22 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.accountcheck.testonly"
-    android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
+     package="com.android.cts.devicepolicy.accountcheck.testonly"
+     android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
 
     <application android:testOnly="true">
-        <receiver
-            android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
     <!--
-      Don't need instrumentation. All the three device side apps have the same UID, so we're able
-      to run all tests from the Auth package.
-    -->
+              Don't need instrumentation. All the three device side apps have the same UID, so we're able
+              to run all tests from the Auth package.
+            -->
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwnerUpdate/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwnerUpdate/AndroidManifest.xml
index cd186e9..e874e35 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwnerUpdate/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwnerUpdate/AndroidManifest.xml
@@ -14,26 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  -->
-
 <!-- This package is exactly same as TestOnlyOwner, except for testOnly=false -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.accountcheck.testonly"
-    android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
+     package="com.android.cts.devicepolicy.accountcheck.testonly"
+     android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
 
     <application android:testOnly="false">
-        <receiver
-            android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
     <!--
-      Don't need instrumentation. All the three device side apps have the same UID, so we're able
-      to run all tests from the Auth package.
-    -->
+              Don't need instrumentation. All the three device side apps have the same UID, so we're able
+              to run all tests from the Auth package.
+            -->
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
index 711f984..da14401 100644
--- a/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
@@ -16,23 +16,24 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.autofillapp" >
+     package="com.android.cts.devicepolicy.autofillapp">
 
     <application>
-        <activity android:name=".SimpleActivity" android:exported="true">
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name=".SimpleAutofillService"
-            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+        <service android:name=".SimpleAutofillService"
+             android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
         </service>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
index df47d0b..89c72ed 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
@@ -15,44 +15,42 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.certinstaller">
+     package="com.android.cts.certinstaller">
 
     <uses-sdk android:minSdkVersion="22"/>
 
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <receiver android:name=".CertInstallerReceiver">
+        <receiver android:name=".CertInstallerReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.certinstaller.install_cert" />
-                <action android:name="com.android.cts.certinstaller.remove_cert" />
-                <action android:name="com.android.cts.certinstaller.verify_cert" />
-                <action android:name="com.android.cts.certinstaller.install_keypair" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.certinstaller.install_cert"/>
+                <action android:name="com.android.cts.certinstaller.remove_cert"/>
+                <action android:name="com.android.cts.certinstaller.verify_cert"/>
+                <action android:name="com.android.cts.certinstaller.install_keypair"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="android.app.Activity"
-            android:exported="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
         </activity>
-        <receiver
-            android:name=".CertSelectionDelegateTest$CertSelectionReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name=".CertSelectionDelegateTest$CertSelectionReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.CHOOSE_PRIVATE_KEY_ALIAS"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="Delegated Cert Installer CTS test"
-        android:targetPackage="com.android.cts.certinstaller">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Delegated Cert Installer CTS test"
+         android:targetPackage="com.android.cts.certinstaller">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/ContentCaptureService/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ContentCaptureService/AndroidManifest.xml
index 0710df7..fa4be0a 100644
--- a/hostsidetests/devicepolicy/app/ContentCaptureService/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ContentCaptureService/AndroidManifest.xml
@@ -16,16 +16,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.contentcaptureservice" >
+     package="com.android.cts.devicepolicy.contentcaptureservice">
 
     <application>
-        <service
-            android:name=".SimpleContentCaptureService"
-            android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE">
+        <service android:name=".SimpleContentCaptureService"
+             android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.contentcapture.ContentCaptureService" />
+                <action android:name="android.service.contentcapture.ContentCaptureService"/>
             </intent-filter>
         </service>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/ContentSuggestionsApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ContentSuggestionsApp/AndroidManifest.xml
index c42469b..8b78d27 100644
--- a/hostsidetests/devicepolicy/app/ContentSuggestionsApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ContentSuggestionsApp/AndroidManifest.xml
@@ -16,23 +16,24 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.contentsuggestionsapp" >
+     package="com.android.cts.devicepolicy.contentsuggestionsapp">
 
     <application>
-        <activity android:name=".SimpleActivity" android:exported="true">
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name=".SimpleContentSuggestionsService"
-            android:permission="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE">
+        <service android:name=".SimpleContentSuggestionsService"
+             android:permission="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.contentsuggestions.ContentSuggestionsService" />
+                <action android:name="android.service.contentsuggestions.ContentSuggestionsService"/>
             </intent-filter>
         </service>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/AndroidManifest.xml
index b24e8c8..522153c 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/AndroidManifest.xml
@@ -15,21 +15,18 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.comp" >
+     package="com.android.cts.comp">
     <!-- package="com.android.cts.comp2"
-         We have com.android.cts.comp2 that have the exact same source but with different package
-         name, see Android.mk for details. -->
-    <application
-        android:testOnly="true">
+                 We have com.android.cts.comp2 that have the exact same source but with different package
+                 name, see Android.mk for details. -->
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name="com.android.cts.comp.AdminReceiver"
-                android:exported="true"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
-            <meta-data
-                    android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin"/>
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.comp.AdminReceiver"
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
                 <action android:name="android.app.action.PROFILE_PROVISIONING_COMPLETE"/>
@@ -38,22 +35,23 @@
         <activity android:name="com.android.compatibility.common.util.devicepolicy.provisioning.StartProvisioningActivity"/>
 
         <service android:name=".ProtectedCrossUserService"
-                android:exported="true"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
         </service>
 
         <service android:name=".UnprotectedCrossUserService"
-                android:exported="true">
+             android:exported="true">
         </service>
 
-        <receiver android:name=".WipeDataReceiver">
+        <receiver android:name=".WipeDataReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.comp.WIPE_DATA" />
+                <action android:name="com.android.cts.comp.WIPE_DATA"/>
             </intent-filter>
         </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.comp"
-            android:label="Corp owned managed profile CTS tests"/>
+         android:targetPackage="com.android.cts.comp"
+         android:label="Corp owned managed profile CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml
index ae3ff05..afe1d9d 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml
@@ -15,23 +15,25 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.crossprofileappstest">
+     package="com.android.cts.crossprofileappstest">
 
-    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25"/>
+    <uses-sdk android:minSdkVersion="21"
+         android:targetSdkVersion="25"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.crossprofileappstest.AdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.crossprofileappstest.AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <activity android:name=".MainActivity" android:exported="true">
+        <activity android:name=".MainActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -39,13 +41,15 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".NonMainActivity" android:exported="true">
+        <activity android:name=".NonMainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="nonMainActivity" />
+                <action android:name="nonMainActivity"/>
             </intent-filter>
         </activity>
 
-        <activity android:name=".NonExportedActivity" android:exported="false">
+        <activity android:name=".NonExportedActivity"
+             android:exported="false">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -53,12 +57,13 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".CrossProfileSameTaskLauncherActivity" android:exported="true"/>
+        <activity android:name=".CrossProfileSameTaskLauncherActivity"
+             android:exported="true"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.crossprofileappstest"
-                     android:label="Launcher Apps CTS Tests"/>
+         android:targetPackage="com.android.cts.crossprofileappstest"
+         android:label="Launcher Apps CTS Tests"/>
 
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/AndroidManifest.xml
index f3fde4c..2729fda 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/AndroidManifest.xml
@@ -16,24 +16,25 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.crossprofileenabledapp">
+     package="com.android.cts.crossprofileenabledapp">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
-    <application
-        android:crossProfile="true">
-        <receiver android:name=".CrossProfileEnabledAppReceiver">
+    <application android:crossProfile="true">
+        <receiver android:name=".CrossProfileEnabledAppReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED" />
+                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED"/>
             </intent-filter>
         </receiver>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.crossprofileenabledapp"
-                     android:label="Launcher Apps CTS Tests"/>
+         android:targetPackage="com.android.cts.crossprofileenabledapp"
+         android:label="Launcher Apps CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/AndroidManifest.xml
index 243fa23..f7baec4 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/AndroidManifest.xml
@@ -16,25 +16,26 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.crossprofileenablednopermsapp">
+     package="com.android.cts.crossprofileenablednopermsapp">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <!-- We need to request the permission, which is denied in the test. -->
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
 
-    <application
-        android:crossProfile="true">
-        <receiver android:name=".CrossProfileEnabledNoPermsAppReceiver">
+    <application android:crossProfile="true">
+        <receiver android:name=".CrossProfileEnabledNoPermsAppReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED" />
+                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED"/>
             </intent-filter>
         </receiver>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.crossprofileenablednopermsapp"
-                     android:label="Launcher Apps CTS Tests"/>
+         android:targetPackage="com.android.cts.crossprofileenablednopermsapp"
+         android:label="Launcher Apps CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/AndroidManifest.xml
index 6af733e..d616942 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/AndroidManifest.xml
@@ -16,26 +16,27 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.crossprofilenotenabledapp">
+     package="com.android.cts.crossprofilenotenabledapp">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
 
-    <application
-        android:crossProfile="false">
-        <receiver android:name=".CrossProfileNotEnabledAppReceiver">
+    <application android:crossProfile="false">
+        <receiver android:name=".CrossProfileNotEnabledAppReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED" />
+                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED"/>
             </intent-filter>
         </receiver>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.crossprofilenotenabledapp"
-                     android:label="Launcher Apps CTS Tests"/>
+         android:targetPackage="com.android.cts.crossprofilenotenabledapp"
+         android:label="Launcher Apps CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/AndroidManifest.xml
index c10c617..d89a88a 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/AndroidManifest.xml
@@ -16,24 +16,25 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.crossprofileuserenabledapp">
+     package="com.android.cts.crossprofileuserenabledapp">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
-    <application
-        android:crossProfile="true">
-        <receiver android:name=".CrossProfileUserEnabledAppReceiver">
+    <application android:crossProfile="true">
+        <receiver android:name=".CrossProfileUserEnabledAppReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED" />
+                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED"/>
             </intent-filter>
         </receiver>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.crossprofileuserenabledapp"
-                     android:label="Launcher Apps CTS Tests"/>
+         android:targetPackage="com.android.cts.crossprofileuserenabledapp"
+         android:label="Launcher Apps CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
index 33da314..319daca 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
@@ -15,25 +15,24 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.delegate">
+     package="com.android.cts.delegate">
 
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <application android:usesCleartextTraffic="true">
-        <uses-library android:name="android.test.runner" />
-        <activity
-            android:name="com.android.cts.delegate.DelegatedScopesReceiverActivity"
-            android:exported="true">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="com.android.cts.delegate.DelegatedScopesReceiverActivity"
+             android:exported="true">
         </activity>
-        <receiver
-            android:name=".NetworkLoggingDelegateTest$NetworkLogsReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name=".NetworkLoggingDelegateTest$NetworkLogsReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.NETWORK_LOGS_AVAILABLE"/>
             </intent-filter>
         </receiver>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.delegate"
-                     android:label="Delegation CTS Tests"/>
+         android:targetPackage="com.android.cts.delegate"
+         android:label="Delegation CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java
index 55fc792..7fc8109 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java
@@ -89,12 +89,12 @@
                 amIAppRestrictionsDelegate());
 
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setApplicationRestrictions(null, APP_RESTRICTIONS_TARGET_PKG, null);
                 });
 
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.getApplicationRestrictions(null, APP_RESTRICTIONS_TARGET_PKG);
                 });
     }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java
index f706b85..26afac9 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java
@@ -48,7 +48,7 @@
             amIBlockUninstallDelegate());
 
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setUninstallBlocked(null, TEST_APP_PKG, true);
                 });
     }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java
index 933e257..dbe8cc5 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java
@@ -124,12 +124,12 @@
         assertFalse(amICertInstallDelegate());
 
         assertExpectException(SecurityException.class,
-                "Neither user \\d+ nor current process has", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.installCaCert(null, null);
                 });
 
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.removeKeyPair(null, "alias");
                 });
     }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/EnableSystemAppDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/EnableSystemAppDelegateTest.java
index 246f936..21e3f7c 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/EnableSystemAppDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/EnableSystemAppDelegateTest.java
@@ -51,13 +51,13 @@
 
         // Exercise enableSystemApp(String).
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.enableSystemApp(null, TEST_APP_PKG);
                 });
 
         // Exercise enableSystemApp(Intent).
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.enableSystemApp(null, new Intent().setPackage(TEST_APP_PKG));
                 });
     }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PackageAccessDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PackageAccessDelegateTest.java
index 86f2639..05c2270 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PackageAccessDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PackageAccessDelegateTest.java
@@ -51,25 +51,25 @@
 
         // Exercise isApplicationHidden.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.isApplicationHidden(null, TEST_APP_PKG);
                 });
 
         // Exercise setApplicationHidden.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setApplicationHidden(null, TEST_APP_PKG, true /* hide */);
                 });
 
         // Exercise isPackageSuspended.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.isPackageSuspended(null, TEST_APP_PKG);
                 });
 
         // Exercise setPackagesSuspended.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setPackagesSuspended(null, new String[] {TEST_APP_PKG}, true /* suspend */);
                 });
     }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PermissionGrantDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PermissionGrantDelegateTest.java
index 81b74a2..55b6a5a 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PermissionGrantDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PermissionGrantDelegateTest.java
@@ -55,7 +55,7 @@
 
         // Exercise setPermissionPolicy.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setPermissionPolicy(null, PERMISSION_POLICY_AUTO_GRANT);
                 });
         assertFalse("Permission policy should not have been set",
@@ -63,14 +63,14 @@
 
         // Exercise setPermissionGrantState.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setPermissionGrantState(null, TEST_APP_PKG, TEST_PERMISSION,
                             PERMISSION_GRANT_STATE_GRANTED);
                 });
 
         // Exercise getPermissionGrantState.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.getPermissionGrantState(null, TEST_APP_PKG, TEST_PERMISSION);
                 });
     }
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api23/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdmin/api23/AndroidManifest.xml
index 8e2fdc2..ecb9b7a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api23/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api23/AndroidManifest.xml
@@ -15,38 +15,36 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadmin23" >
+     package="com.android.cts.deviceadmin23">
 
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="23"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <receiver
-                android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
-                >
+        <receiver android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadmin23"
-            android:label="Device Admin CTS tests"/>
+         android:targetPackage="com.android.cts.deviceadmin23"
+         android:label="Device Admin CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api24/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdmin/api24/AndroidManifest.xml
index 30bd6dc..d368801 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api24/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api24/AndroidManifest.xml
@@ -15,38 +15,36 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadmin24" >
+     package="com.android.cts.deviceadmin24">
 
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="24"/>
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="24"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <receiver
-                android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
-                >
+        <receiver android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadmin24"
-            android:label="Device Admin CTS tests"/>
+         android:targetPackage="com.android.cts.deviceadmin24"
+         android:label="Device Admin CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api29/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdmin/api29/AndroidManifest.xml
index 326e61f..5af6d62 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api29/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api29/AndroidManifest.xml
@@ -15,38 +15,36 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadmin29" >
+     package="com.android.cts.deviceadmin29">
 
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="29"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <receiver
-                android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
-                >
+        <receiver android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadmin29"
-            android:label="Device Admin CTS tests"/>
+         android:targetPackage="com.android.cts.deviceadmin29"
+         android:label="Device Admin CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package1/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdminService/package1/AndroidManifest.xml
index d2b4b0c..1bd9261 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package1/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package1/AndroidManifest.xml
@@ -15,32 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadminservice" >
+     package="com.android.cts.deviceadminservice">
 
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name=".MyOwner"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".MyOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <service
-                android:name=".MyService"
-                android:exported="true"
-                android:permission="android.permission.BIND_DEVICE_ADMIN" >
+        <service android:name=".MyService"
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadminservice" />
+         android:targetPackage="com.android.cts.deviceadminservice"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package2/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdminService/package2/AndroidManifest.xml
index c57eb8e..83afcee 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package2/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package2/AndroidManifest.xml
@@ -15,32 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadminservice" >
+     package="com.android.cts.deviceadminservice">
 
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name=".MyOwner"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".MyOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <service
-                android:name=".MyService"
-                android:exported="false"
-                android:permission="android.permission.BIND_DEVICE_ADMIN" >
+        <service android:name=".MyService"
+             android:exported="false"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadminservice" />
+         android:targetPackage="com.android.cts.deviceadminservice"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package3/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdminService/package3/AndroidManifest.xml
index 46a5fee..2801351 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package3/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package3/AndroidManifest.xml
@@ -15,31 +15,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadminservice" >
+     package="com.android.cts.deviceadminservice">
 
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name=".MyOwner"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".MyOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <service
-                android:name=".MyService"
-                android:exported="false">
+        <service android:name=".MyService"
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadminservice" />
+         android:targetPackage="com.android.cts.deviceadminservice"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package4/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdminService/package4/AndroidManifest.xml
index 3a98de5..bea0cb7 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package4/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package4/AndroidManifest.xml
@@ -15,37 +15,36 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadminservice" >
+     package="com.android.cts.deviceadminservice">
 
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name=".MyOwner"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".MyOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <service
-                android:name=".MyService"
-                android:permission="android.permission.BIND_DEVICE_ADMIN" >
+        <service android:name=".MyService"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
-        <service
-                android:name=".MyService2"
-                android:permission="android.permission.BIND_DEVICE_ADMIN" >
+        <service android:name=".MyService2"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadminservice" />
+         android:targetPackage="com.android.cts.deviceadminservice"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/AndroidManifest.xml
index beb23a8..d21cea6 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/AndroidManifest.xml
@@ -15,30 +15,28 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadminserviceb" >
+     package="com.android.cts.deviceadminserviceb">
 
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name="com.android.cts.deviceadminservice.MyOwner"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceadminservice.MyOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <service
-                android:name="com.android.cts.deviceadminservice.MyService"
-                android:exported="true"
-                android:permission="android.permission.BIND_DEVICE_ADMIN" >
+        <service android:name="com.android.cts.deviceadminservice.MyService"
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadminservice" />
+         android:targetPackage="com.android.cts.deviceadminservice"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
index 3abd12d..3263538 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
@@ -28,6 +28,7 @@
         "cts-security-test-support-library",
         "androidx.legacy_legacy-support-v4",
         "cts-devicepolicy-suspensionchecker",
+        "devicepolicy-deviceside-common",
     ],
     resource_dirs: ["res"],
     asset_dirs: ["assets"],
@@ -55,6 +56,7 @@
         "cts-security-test-support-library",
         "androidx.legacy_legacy-support-v4",
         "cts-devicepolicy-suspensionchecker",
+        "devicepolicy-deviceside-common",
     ],
     resource_dirs: ["res"],
     asset_dirs: ["assets"],
@@ -83,6 +85,7 @@
         "cts-security-test-support-library",
         "androidx.legacy_legacy-support-v4",
         "cts-devicepolicy-suspensionchecker",
+        "devicepolicy-deviceside-common",
     ],
     resource_dirs: ["res"],
     asset_dirs: ["assets"],
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/AndroidManifest.xml
index 9580f16..b6ebb20 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/AndroidManifest.xml
@@ -15,37 +15,35 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.deviceandprofileowner">
+     package="com.android.cts.deviceandprofileowner">
 
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="23"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-            android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:label="Profile and Device Owner CTS Tests API 23"
-            android:targetPackage="com.android.cts.deviceandprofileowner">
-        <meta-data
-                android:name="listener"
-                android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Profile and Device Owner CTS Tests API 23"
+         android:targetPackage="com.android.cts.deviceandprofileowner">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/AndroidManifest.xml
index 618284e..4010d31 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/AndroidManifest.xml
@@ -15,34 +15,33 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.deviceandprofileowner">
+     package="com.android.cts.deviceandprofileowner">
 
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="25"/>
-    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="25"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
 
     <!-- Add a network security config that trusts user added CAs for tests -->
     <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN"
-            android:directBootAware="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:directBootAware="true"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
     </application>
 
-    <instrumentation
-            android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:label="Profile and Device Owner CTS Tests"
-            android:targetPackage="com.android.cts.deviceandprofileowner">
-        <meta-data
-                android:name="listener"
-                android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Profile and Device Owner CTS Tests"
+         android:targetPackage="com.android.cts.deviceandprofileowner">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
index 9f7660a..ac91539 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
@@ -15,63 +15,63 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.deviceandprofileowner">
+     package="com.android.cts.deviceandprofileowner">
 
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.SET_WALLPAPER" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.SET_WALLPAPER"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
     <!-- Needed to read the serial number during Device ID attestation tests -->
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
     <!-- Add a network security config that trusts user added CAs for tests -->
     <application android:networkSecurityConfig="@xml/network_security_config"
-        android:testOnly="true">
+         android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN"
-            android:directBootAware="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:directBootAware="true"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="com.android.cts.deviceandprofileowner.ExampleIntentReceivingActivity1">
+        <activity android:name="com.android.cts.deviceandprofileowner.ExampleIntentReceivingActivity1"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.deviceandprofileowner.EXAMPLE_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.deviceandprofileowner.EXAMPLE_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="com.android.cts.deviceandprofileowner.ExampleIntentReceivingActivity2">
+        <activity android:name="com.android.cts.deviceandprofileowner.ExampleIntentReceivingActivity2"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.deviceandprofileowner.EXAMPLE_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.deviceandprofileowner.EXAMPLE_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name=".SetPolicyActivity"
-            android:launchMode="singleTop">
+        <activity android:name=".SetPolicyActivity"
+             android:launchMode="singleTop"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
@@ -84,52 +84,57 @@
 
         <activity android:name=".PrintActivity"/>
 
-        <activity
-            android:name="com.android.cts.deviceandprofileowner.KeyManagementActivity"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+        <activity android:name="com.android.cts.deviceandprofileowner.KeyManagementActivity"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
 
-        <activity
-            android:name="com.android.cts.deviceandprofileowner.LockTaskUtilityActivity"/>
-        <activity
-            android:name="com.android.cts.deviceandprofileowner.LockTaskUtilityActivityIfWhitelisted"
-            android:launchMode="singleInstance"
-            android:lockTaskMode="if_whitelisted">
+        <activity android:name="com.android.cts.deviceandprofileowner.LockTaskUtilityActivity"/>
+        <activity android:name="com.android.cts.deviceandprofileowner.LockTaskUtilityActivityIfWhitelisted"
+             android:launchMode="singleInstance"
+             android:lockTaskMode="if_whitelisted"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity
-            android:name="android.app.Activity">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.action.CHECK_POLICY_COMPLIANCE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.app.action.CHECK_POLICY_COMPLIANCE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <receiver android:name=".WipeDataReceiver">
+        <receiver android:name=".WipeDataReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.deviceandprofileowner.WIPE_DATA" />
+                <action android:name="com.android.cts.deviceandprofileowner.WIPE_DATA"/>
             </intent-filter>
         </receiver>
 
         <service android:name=".NotificationListener"
-            android:exported="true"
-            android:label="Notification Listener"
-            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+             android:exported="true"
+             android:label="Notification Listener"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
+            </intent-filter>
+        </service>
+
+        <service
+            android:name=".SimpleKeyguardService"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE" />
             </intent-filter>
         </service>
     </application>
 
-    <instrumentation
-            android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:label="Profile and Device Owner CTS Tests"
-            android:targetPackage="com.android.cts.deviceandprofileowner">
-        <meta-data
-                android:name="listener"
-                android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Profile and Device Owner CTS Tests"
+         android:targetPackage="com.android.cts.deviceandprofileowner">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationRestrictionsTest.java
index 652c908..520f7be 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationRestrictionsTest.java
@@ -112,7 +112,7 @@
             fail("Expected SecurityException not thrown");
         } catch (SecurityException expected) {
             MoreAsserts.assertContainsRegex(
-                    "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+                    "Calling identity is not authorized",
                     expected.getMessage());
         }
         try {
@@ -120,7 +120,7 @@
             fail("Expected SecurityException not thrown");
         } catch (SecurityException expected) {
             MoreAsserts.assertContainsRegex(
-                    "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+                    "Calling identity is not authorized",
                     expected.getMessage());
         }
     }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java
index 791f207..7097050 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.cts.deviceandprofileowner;
 
+import static com.android.compatibility.common.util.FakeKeys.FAKE_DSA_1;
+import static com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
+
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.net.http.X509TrustManagerExtensions;
 
@@ -32,9 +36,6 @@
 import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
 
-import static com.android.compatibility.common.util.FakeKeys.FAKE_DSA_1;
-import static com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
-
 public class CaCertManagementTest extends BaseDeviceAdminTest {
     private final ComponentName mAdmin = ADMIN_RECEIVER_COMPONENT;
 
@@ -48,19 +49,30 @@
         assertNotNull(caCerts);
     }
 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        uninstallAllUserCaCerts();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallAllUserCaCerts();
+        super.tearDown();
+    }
+
     /**
      * Test: a valid cert should be installable and also removable.
      */
-    public void testCanInstallAndUninstallACaCert()
-            throws CertificateException, GeneralSecurityException {
+    public void testCanInstallAndUninstallACaCert() throws GeneralSecurityException {
         assertUninstalled(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_DSA_1.caCertificate);
 
-        assertTrue(mDevicePolicyManager.installCaCert(mAdmin, FAKE_RSA_1.caCertificate));
+        assertTrue(installCaCert(FAKE_RSA_1.caCertificate));
         assertInstalled(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_DSA_1.caCertificate);
 
-        mDevicePolicyManager.uninstallCaCert(mAdmin, FAKE_RSA_1.caCertificate);
+        uninstallCaCert(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_DSA_1.caCertificate);
     }
@@ -68,42 +80,36 @@
     /**
      * Test: removing one certificate must not remove any others.
      */
-    public void testUninstallationIsSelective()
-            throws CertificateException, GeneralSecurityException {
-        assertTrue(mDevicePolicyManager.installCaCert(mAdmin, FAKE_RSA_1.caCertificate));
-        assertTrue(mDevicePolicyManager.installCaCert(mAdmin, FAKE_DSA_1.caCertificate));
+    public void testUninstallationIsSelective() throws GeneralSecurityException {
+        assertTrue(installCaCert(FAKE_RSA_1.caCertificate));
+        assertTrue(installCaCert(FAKE_DSA_1.caCertificate));
 
-        mDevicePolicyManager.uninstallCaCert(mAdmin, FAKE_DSA_1.caCertificate);
+        uninstallCaCert(FAKE_DSA_1.caCertificate);
         assertInstalled(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_DSA_1.caCertificate);
 
-        mDevicePolicyManager.uninstallCaCert(mAdmin, FAKE_RSA_1.caCertificate);
+        uninstallCaCert(FAKE_RSA_1.caCertificate);
     }
 
     /**
      * Test: uninstallAllUserCaCerts should be equivalent to calling uninstallCaCert on every
      * supplementary installed certificate.
      */
-    public void testCanUninstallAllUserCaCerts()
-            throws CertificateException, GeneralSecurityException {
-        assertTrue(mDevicePolicyManager.installCaCert(mAdmin, FAKE_RSA_1.caCertificate));
-        assertTrue(mDevicePolicyManager.installCaCert(mAdmin, FAKE_DSA_1.caCertificate));
+    public void testCanUninstallAllUserCaCerts() throws GeneralSecurityException {
+        assertTrue(installCaCert(FAKE_RSA_1.caCertificate));
+        assertTrue(installCaCert(FAKE_DSA_1.caCertificate));
 
-        mDevicePolicyManager.uninstallAllUserCaCerts(mAdmin);
+        uninstallAllUserCaCerts();
         assertUninstalled(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_DSA_1.caCertificate);
     }
 
-    private void assertInstalled(byte[] caBytes)
-            throws CertificateException, GeneralSecurityException {
-        Certificate caCert = readCertificate(caBytes);
-        assertTrue(isCaCertInstalledAndTrusted(caCert));
+    private void assertInstalled(byte[] caBytes) throws GeneralSecurityException {
+        assertTrue(isCaCertInstalledAndTrusted(caBytes));
     }
 
-    private void assertUninstalled(byte[] caBytes)
-            throws CertificateException, GeneralSecurityException {
-        Certificate caCert = readCertificate(caBytes);
-        assertFalse(isCaCertInstalledAndTrusted(caCert));
+    private void assertUninstalled(byte[] caBytes) throws GeneralSecurityException {
+        assertFalse(isCaCertInstalledAndTrusted(caBytes));
     }
 
     private static X509TrustManager getFirstX509TrustManager(TrustManagerFactory tmf) {
@@ -131,8 +137,8 @@
      * @return {@code true} if installed by all metrics, {@code false} if not installed by any
      *         metric. In any other case an {@link AssertionError} will be thrown.
      */
-    private boolean isCaCertInstalledAndTrusted(Certificate caCert)
-            throws GeneralSecurityException, CertificateException {
+    private boolean isCaCertInstalledAndTrusted(byte[] caBytes) throws GeneralSecurityException {
+        Certificate caCert = readCertificate(caBytes);
         boolean installed = mDevicePolicyManager.hasCaCertInstalled(mAdmin, caCert.getEncoded());
 
         boolean listed = false;
@@ -184,4 +190,16 @@
         final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
         return certFactory.generateCertificate(new ByteArrayInputStream(certBuffer));
     }
+
+    private boolean installCaCert(byte[] caCertificate) {
+        return mDevicePolicyManager.installCaCert(mAdmin, caCertificate);
+    }
+
+    private void uninstallCaCert(byte[] caCertificate) {
+        mDevicePolicyManager.uninstallCaCert(mAdmin, caCertificate);
+    }
+
+    private void uninstallAllUserCaCerts() {
+        mDevicePolicyManager.uninstallAllUserCaCerts(mAdmin);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CameraUtils.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CameraUtils.java
deleted file mode 100644
index d23d4b3..0000000
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CameraUtils.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.deviceandprofileowner;
-
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.os.Handler;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A util class to help open camera in a blocking way.
- */
-class CameraUtils {
-
-    private static final String TAG = "CameraUtils";
-
-    /**
-     * @return true if success to open camera, false otherwise.
-     */
-    public static boolean blockUntilOpenCamera(CameraManager cameraManager, Handler handler) {
-        try {
-            String[] cameraIdList = cameraManager.getCameraIdList();
-            if (cameraIdList == null || cameraIdList.length == 0) {
-                return false;
-            }
-            String cameraId = cameraIdList[0];
-            CameraCallback callback = new CameraCallback();
-            cameraManager.openCamera(cameraId, callback, handler);
-            return callback.waitForResult();
-        } catch (Exception ex) {
-            // No matter what is going wrong, it means fail to open camera.
-            ex.printStackTrace();
-            return false;
-        }
-    }
-
-    /**
-     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
-     */
-    private static class CameraCallback extends CameraDevice.StateCallback {
-
-        private static final int OPEN_TIMEOUT_SECONDS = 5;
-
-        private final CountDownLatch mLatch = new CountDownLatch(1);
-
-        private AtomicBoolean mResult = new AtomicBoolean(false);
-
-        @Override
-        public void onOpened(CameraDevice cameraDevice) {
-            Log.d(TAG, "open camera successfully");
-            mResult.set(true);
-            if (cameraDevice != null) {
-                cameraDevice.close();
-            }
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onDisconnected(CameraDevice cameraDevice) {
-            Log.d(TAG, "disconnect camera");
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onError(CameraDevice cameraDevice, int error) {
-            Log.e(TAG, "Fail to open camera, error code = " + error);
-            mLatch.countDown();
-        }
-
-        public boolean waitForResult() throws InterruptedException {
-            mLatch.await(OPEN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
-            return mResult.get();
-        }
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearProfileOwnerNegativeTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearProfileOwnerNegativeTest.java
index 30ae0cd..b61b546 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearProfileOwnerNegativeTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearProfileOwnerNegativeTest.java
@@ -33,7 +33,7 @@
             try {
                 mDevicePolicyManager.clearProfileOwner(BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT);
             } catch (SecurityException e) {
-                MoreAsserts.assertContainsRegex("clear profile owner", e.getMessage());
+                MoreAsserts.assertContainsRegex("Calling user is not authorized", e.getMessage());
             }
         }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
index f42db09..9af9bb1 100755
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
@@ -379,7 +379,7 @@
         Attestation attestationRecord = new Attestation((X509Certificate) leaf);
         AuthorizationList teeAttestation = attestationRecord.getTeeEnforced();
         assertThat(teeAttestation).isNotNull();
-        validateBrandAttestationRecord(teeAttestation);
+        assertThat(teeAttestation.getBrand()).isEqualTo(Build.BRAND);
         assertThat(teeAttestation.getDevice()).isEqualTo(Build.DEVICE);
         assertThat(teeAttestation.getProduct()).isEqualTo(Build.PRODUCT);
         assertThat(teeAttestation.getManufacturer()).isEqualTo(Build.MANUFACTURER);
@@ -389,14 +389,6 @@
         assertThat(teeAttestation.getMeid()).isEqualTo(expectedMeid);
     }
 
-    private void validateBrandAttestationRecord(AuthorizationList teeAttestation) {
-        if (!Build.MODEL.equals("Pixel 2")) {
-            assertThat(teeAttestation.getBrand()).isEqualTo(Build.BRAND);
-        } else {
-            assertThat(teeAttestation.getBrand()).isAnyOf(Build.BRAND, "htc");
-        }
-    }
-
     private void validateAttestationRecord(List<Certificate> attestation, byte[] providedChallenge)
             throws CertificateParsingException {
         assertThat(attestation).isNotNull();
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
index 87d6fd5..844b710 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
@@ -17,31 +17,30 @@
 
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.Manifest.permission.WRITE_CONTACTS;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY;
+import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_PROMPT;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest.permission;
-import android.app.AppOpsManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.UiWatcher;
-import android.support.test.uiautomator.Until;
 import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
+import com.android.cts.devicepolicy.PermissionBroadcastReceiver;
+import com.android.cts.devicepolicy.PermissionUtils;
+
 import com.google.android.collect.Sets;
 
 import java.util.Set;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -49,29 +48,21 @@
  * Test Runtime Permissions APIs in DevicePolicyManager.
  */
 public class PermissionsTest extends BaseDeviceAdminTest {
+
     private static final String TAG = "PermissionsTest";
 
-    private static final String PERMISSION_APP_PACKAGE_NAME
-            = "com.android.cts.permissionapp";
-    private static final String SIMPLE_PRE_M_APP_PACKAGE_NAME =
-            "com.android.cts.launcherapps.simplepremapp";
+    private static final String PERMISSION_APP_PACKAGE_NAME = "com.android.cts.permissionapp";
+    private static final String PRE_M_APP_PACKAGE_NAME
+            = "com.android.cts.launcherapps.simplepremapp";
+    private static final String PERMISSIONS_ACTIVITY_NAME
+            = PERMISSION_APP_PACKAGE_NAME + ".PermissionActivity";
     private static final String CUSTOM_PERM_A_NAME = "com.android.cts.permissionapp.permA";
     private static final String CUSTOM_PERM_B_NAME = "com.android.cts.permissionapp.permB";
     private static final String DEVELOPMENT_PERMISSION = "android.permission.INTERACT_ACROSS_USERS";
 
-    private static final String PERMISSIONS_ACTIVITY_NAME
-            = PERMISSION_APP_PACKAGE_NAME + ".PermissionActivity";
-    private static final String ACTION_CHECK_HAS_PERMISSION
-            = "com.android.cts.permission.action.CHECK_HAS_PERMISSION";
-    private static final String ACTION_REQUEST_PERMISSION
-            = "com.android.cts.permission.action.REQUEST_PERMISSION";
     private static final String ACTION_PERMISSION_RESULT
             = "com.android.cts.permission.action.PERMISSION_RESULT";
-    private static final String EXTRA_PERMISSION
-            = "com.android.cts.permission.extra.PERMISSION";
-    private static final String EXTRA_GRANT_STATE
-            = "com.android.cts.permission.extra.GRANT_STATE";
-    private static final int PERMISSION_ERROR = -2;
+
     private static final BySelector CRASH_POPUP_BUTTON_SELECTOR = By
             .clazz(android.widget.Button.class.getName())
             .text("OK")
@@ -80,17 +71,15 @@
             .clazz(android.widget.TextView.class.getName())
             .pkg("android");
     private static final String CRASH_WATCHER_ID = "CRASH";
+    private static final String AUTO_GRANTED_PERMISSIONS_CHANNEL_ID =
+            "alerting auto granted permissions";
 
     private static final Set<String> LOCATION_PERMISSIONS = Sets.newHashSet(
             permission.ACCESS_FINE_LOCATION,
             permission.ACCESS_BACKGROUND_LOCATION,
             permission.ACCESS_COARSE_LOCATION);
 
-    public static final String AUTO_GRANTED_PERMISSIONS_CHANNEL_ID =
-            "alerting auto granted permissions";
-
     private PermissionBroadcastReceiver mReceiver;
-    private PackageManager mPackageManager;
     private UiDevice mDevice;
 
     @Override
@@ -98,7 +87,6 @@
         super.setUp();
         mReceiver = new PermissionBroadcastReceiver();
         mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PERMISSION_RESULT));
-        mPackageManager = mContext.getPackageManager();
         mDevice = UiDevice.getInstance(getInstrumentation());
     }
 
@@ -109,391 +97,341 @@
         super.tearDown();
     }
 
-    /** Return values of {@link #checkPermission} */
-    int PERMISSION_DENIED = PackageManager.PERMISSION_DENIED;
-    int PERMISSION_GRANTED = PackageManager.PERMISSION_GRANTED;
-    int PERMISSION_DENIED_APP_OP = PackageManager.PERMISSION_DENIED - 1;
+    public void testPermissionGrantStateDenied() throws Exception {
+        setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
 
-    /**
-     * Correctly check a runtime permission. This also works for pre-m apps.
-     */
-    private int checkPermission(String permission, int uid, String packageName) {
-        if (mContext.checkPermission(permission, -1, uid) == PackageManager.PERMISSION_DENIED) {
-            return PERMISSION_DENIED;
+        assertPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+        assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+    }
+
+    public void testPermissionGrantStateDenied_permissionRemainsDenied() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+
+            assertNoPermissionFromActivity(READ_CONTACTS);
+
+            // Should stay denied
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DEFAULT);
+
+            assertNoPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
         }
+    }
 
-        if (mContext.getSystemService(AppOpsManager.class).noteProxyOpNoThrow(
-                AppOpsManager.permissionToOp(permission), packageName, uid, null, null)
-                != AppOpsManager.MODE_ALLOWED) {
-            return PERMISSION_DENIED_APP_OP;
+    public void testPermissionGrantStateDenied_mixedPolicies() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        int permissionPolicy = mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+
+            // Check no permission by launching an activity and requesting the permission
+            // Should stay denied if grant state is denied
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+            assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+            assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_PROMPT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_PROMPT);
+            assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
+            setPermissionPolicy(permissionPolicy);
         }
-
-        return PERMISSION_GRANTED;
     }
 
-    public void testPermissionGrantState() throws Exception {
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertPermissionGrantState(PackageManager.PERMISSION_DENIED);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
+    public void testPermissionGrantStateDenied_otherPermissionIsGranted() throws Exception {
+        int grantStateA = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, CUSTOM_PERM_A_NAME);
+        int grantStateB = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, CUSTOM_PERM_B_NAME);
+        try {
+            setPermissionGrantState(CUSTOM_PERM_A_NAME, PERMISSION_GRANT_STATE_GRANTED);
+            setPermissionGrantState(CUSTOM_PERM_B_NAME, PERMISSION_GRANT_STATE_DENIED);
 
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        // Should stay denied
-        assertPermissionGrantState(PackageManager.PERMISSION_DENIED);
+            assertPermissionGrantState(CUSTOM_PERM_A_NAME, PERMISSION_GRANT_STATE_GRANTED);
+            assertPermissionGrantState(CUSTOM_PERM_B_NAME, PERMISSION_GRANT_STATE_DENIED);
 
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-        assertPermissionGrantState(PackageManager.PERMISSION_GRANTED);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        // Should stay granted
-        assertPermissionGrantState(PackageManager.PERMISSION_GRANTED);
+            /*
+             * CUSTOM_PERM_A_NAME and CUSTOM_PERM_B_NAME are in the same permission group and one is
+             * granted the other one is not.
+             *
+             * It should not be possible to get the permission that was denied via policy granted by
+             * requesting it.
+             */
+            assertCannotRequestPermissionFromActivity(CUSTOM_PERM_B_NAME);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(CUSTOM_PERM_A_NAME, grantStateA);
+            setPermissionGrantState(CUSTOM_PERM_B_NAME, grantStateB);
+        }
     }
 
-    public void testPermissionPolicy() throws Exception {
-        // reset permission to denied and unlocked
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+    public void testPermissionGrantStateGranted() throws Exception {
+        setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
 
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
-        // permission should be locked, so changing the policy should not change the grant state
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
-
-        // reset permission to denied and unlocked
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-        // permission should be locked, so changing the policy should not change the grant state
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
+        assertPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
+        assertCanRequestPermissionFromActivity(READ_CONTACTS);
     }
 
-    public void testPermissionMixedPolicies() throws Exception {
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
+    public void testPermissionGrantStateGranted_permissionRemainsGranted() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
 
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
+            assertHasPermissionFromActivity(READ_CONTACTS);
 
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
+            // Should stay granted
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DEFAULT);
 
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
+            assertHasPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
+        }
     }
 
-    public void testAutoGrantMultiplePermissionsInGroup() throws Exception {
-        // set policy to autogrant
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        // both permissions should be granted
-        assertPermissionRequest(READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
-        assertPermissionRequest(WRITE_CONTACTS, PackageManager.PERMISSION_GRANTED);
+    public void testPermissionGrantStateGranted_mixedPolicies() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        int permissionPolicy = mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
+
+            // Check permission by launching an activity and requesting the permission
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_PROMPT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_PROMPT);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
+            setPermissionPolicy(permissionPolicy);
+        }
     }
 
-    public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted() throws Exception {
-        assertSetPermissionGrantState(CUSTOM_PERM_A_NAME,
-                DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-        assertSetPermissionGrantState(CUSTOM_PERM_B_NAME,
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-
-        /*
-         * CUSTOM_PERM_A_NAME and CUSTOM_PERM_B_NAME are in the same permission group and one is
-         * granted the other one is not.
-         *
-         * It should not be possible to get the permission that was denied via policy granted by
-         * requesting it.
-         */
-        assertPermissionRequest(CUSTOM_PERM_B_NAME, PackageManager.PERMISSION_DENIED);
-    }
-
-    @Suppress // Flakey.
-    public void testPermissionPrompts() throws Exception {
-        // register a crash watcher
-        mDevice.registerWatcher(CRASH_WATCHER_ID, new UiWatcher() {
-            @Override
-            public boolean checkForCondition() {
-                UiObject2 button = mDevice.findObject(CRASH_POPUP_BUTTON_SELECTOR);
-                if (button != null) {
-                    UiObject2 text = mDevice.findObject(CRASH_POPUP_TEXT_SELECTOR);
-                    Log.d(TAG, "Removing an error dialog: " + text != null ? text.getText() : null);
-                    button.click();
-                    return true;
-                }
-                return false;
-            }
-        });
-        mDevice.runWatchers();
-
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED, "permission_deny_button");
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED, "permission_allow_button");
-    }
-
-    public void testPermissionUpdate_setDeniedState() throws Exception {
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-    }
-
-    public void testPermissionUpdate_setAutoDeniedPolicy() throws Exception {
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
-    }
-
-    public void testPermissionUpdate_checkDenied() throws Exception {
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
-        assertPermissionGrantState(PackageManager.PERMISSION_DENIED);
-    }
-
-    public void testPermissionUpdate_setGrantedState() throws Exception {
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-    }
-
-    public void testPermissionUpdate_setAutoGrantedPolicy() throws Exception {
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-    }
-
-    public void testPermissionUpdate_checkGranted() throws Exception {
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-        assertPermissionGrantState(PackageManager.PERMISSION_GRANTED);
-    }
-
-    public void testPermissionGrantStateAppPreMDeviceAdminPreQ() throws Exception {
-        // These tests are to make sure that pre-M apps are not granted/denied runtime permissions
-        // by a profile owner that targets pre-Q
-        assertCannotSetPermissionGrantStateAppPreM(
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertCannotSetPermissionGrantStateAppPreM(
-                DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-    }
-
-    public void testPermissionGrantStatePreMApp() throws Exception {
-        // These tests are to make sure that pre-M apps can be granted/denied runtime permissions
-        // by a profile owner targets Q or later
-        assertCanSetPermissionGrantStateAppPreM(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertCanSetPermissionGrantStateAppPreM(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-    }
-
-    public void testPermissionGrantState_developmentPermission() throws Exception {
-        assertFailedToSetDevelopmentPermissionGrantState(
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertFailedToSetDevelopmentPermissionGrantState(
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertFailedToSetDevelopmentPermissionGrantState(
-                DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-    }
-
-    public void testUserNotifiedOfLocationPermissionGrant() throws Exception {
+    public void testPermissionGrantStateGranted_userNotifiedOfLocationPermission()
+            throws Exception {
         for (String locationPermission : LOCATION_PERMISSIONS) {
-            CountDownLatch notificationLatch = initLocationPermissionNotificationLatch();
+            // TODO(b/161359841): move NotificationListener to app/common
+            CountDownLatch notificationLatch = initPermissionNotificationLatch();
 
-            assertSetPermissionGrantState(locationPermission,
-                    DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+            setPermissionGrantState(locationPermission, PERMISSION_GRANT_STATE_GRANTED);
 
-            assertPermissionGrantState(locationPermission, PackageManager.PERMISSION_GRANTED);
+            assertPermissionGrantState(locationPermission, PERMISSION_GRANT_STATE_GRANTED);
             assertTrue(String.format("Did not receive notification for permission %s",
                     locationPermission), notificationLatch.await(60, TimeUnit.SECONDS));
             NotificationListener.getInstance().clearListeners();
         }
     }
 
-    private void assertPermissionRequest(int expected) throws Exception {
-        assertPermissionRequest(READ_CONTACTS, expected);
+    public void testPermissionGrantState_developmentPermission() {
+        assertCannotSetPermissionGrantStateDevelopmentPermission(PERMISSION_GRANT_STATE_DENIED);
+        assertCannotSetPermissionGrantStateDevelopmentPermission(PERMISSION_GRANT_STATE_DEFAULT);
+        assertCannotSetPermissionGrantStateDevelopmentPermission(PERMISSION_GRANT_STATE_GRANTED);
     }
 
-    private void assertPermissionRequest(String permission, int expected) throws Exception {
-        assertPermissionRequest(permission, expected, null);
+    private void assertCannotSetPermissionGrantStateDevelopmentPermission(int value) {
+        unableToSetPermissionGrantState(DEVELOPMENT_PERMISSION, value);
+
+        assertPermissionGrantState(DEVELOPMENT_PERMISSION, PERMISSION_GRANT_STATE_DEFAULT);
+        PermissionUtils.checkPermission(DEVELOPMENT_PERMISSION, PERMISSION_DENIED,
+                PERMISSION_APP_PACKAGE_NAME);
     }
 
-    private void assertPermissionRequest(int expected, String buttonResource)
+    public void testPermissionGrantState_preMApp_preQDeviceAdmin() throws Exception {
+        // These tests are to make sure that pre-M apps are not granted/denied runtime permissions
+        // by a profile owner that targets pre-Q
+        assertCannotSetPermissionGrantStatePreMApp(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+        assertCannotSetPermissionGrantStatePreMApp(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
+    }
+
+    private void assertCannotSetPermissionGrantStatePreMApp(String permission, int value)
             throws Exception {
-        assertPermissionRequest(READ_CONTACTS, expected, buttonResource);
+        assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PRE_M_APP_PACKAGE_NAME, permission, value));
+        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PRE_M_APP_PACKAGE_NAME, permission), PERMISSION_GRANT_STATE_DEFAULT);
+
+        // Install runtime permissions should always be granted
+        PermissionUtils.checkPermission(permission, PERMISSION_GRANTED, PRE_M_APP_PACKAGE_NAME);
+        PermissionUtils.checkPermissionAndAppOps(permission, PERMISSION_GRANTED,
+                PRE_M_APP_PACKAGE_NAME);
     }
 
-    private void assertSetPermissionGrantState(int value) throws Exception {
-        assertSetPermissionGrantState(READ_CONTACTS, value);
+    public void testPermissionGrantState_preMApp() throws Exception {
+        // These tests are to make sure that pre-M apps can be granted/denied runtime permissions
+        // by a profile owner targets Q or later
+        assertCanSetPermissionGrantStatePreMApp(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+        assertCanSetPermissionGrantStatePreMApp(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
     }
 
-    private void assertPermissionGrantState(int expected) throws Exception {
-        assertPermissionGrantState(READ_CONTACTS, expected);
-    }
-
-    private void assertCannotSetPermissionGrantStateAppPreM(int value) throws Exception {
-        assertCannotSetPermissionGrantStateAppPreM(READ_CONTACTS, value);
-    }
-
-    private void assertCanSetPermissionGrantStateAppPreM(int value) throws Exception {
-        assertCanSetPermissionGrantStateAppPreM(READ_CONTACTS, value);
-    }
-
-    private void assertPermissionRequest(String permission, int expected, String buttonResource)
+    private void assertCanSetPermissionGrantStatePreMApp(String permission, int value)
             throws Exception {
-        Intent launchIntent = new Intent();
-        launchIntent.setComponent(new ComponentName(PERMISSION_APP_PACKAGE_NAME,
-                PERMISSIONS_ACTIVITY_NAME));
-        launchIntent.putExtra(EXTRA_PERMISSION, permission);
-        launchIntent.setAction(ACTION_REQUEST_PERMISSION);
-        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-        mContext.startActivity(launchIntent);
-        pressPermissionPromptButton(buttonResource);
-        assertEquals(expected, mReceiver.waitForBroadcast());
-        assertEquals(expected, mPackageManager.checkPermission(permission,
-                PERMISSION_APP_PACKAGE_NAME));
-    }
-
-    private void assertPermissionGrantState(String permission, int expected) throws Exception {
-        assertEquals(expected, mPackageManager.checkPermission(permission,
-                PERMISSION_APP_PACKAGE_NAME));
-        Intent launchIntent = new Intent();
-        launchIntent.setComponent(new ComponentName(PERMISSION_APP_PACKAGE_NAME,
-                PERMISSIONS_ACTIVITY_NAME));
-        launchIntent.putExtra(EXTRA_PERMISSION, permission);
-        launchIntent.setAction(ACTION_CHECK_HAS_PERMISSION);
-        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-        mContext.startActivity(launchIntent);
-        assertEquals(expected, mReceiver.waitForBroadcast());
-    }
-
-    private void assertSetPermissionPolicy(int value) throws Exception {
-        mDevicePolicyManager.setPermissionPolicy(ADMIN_RECEIVER_COMPONENT,
-                value);
-        assertEquals(mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT),
-                value);
-    }
-
-    private void assertSetPermissionGrantState(String permission, int value) throws Exception {
-        mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, permission,
-                value);
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, permission),
-                value);
-    }
-
-    private void assertFailedToSetDevelopmentPermissionGrantState(int value) throws Exception {
-        assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, DEVELOPMENT_PERMISSION, value));
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, DEVELOPMENT_PERMISSION),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertEquals(mPackageManager.checkPermission(DEVELOPMENT_PERMISSION,
-                PERMISSION_APP_PACKAGE_NAME),
-                PackageManager.PERMISSION_DENIED);
-    }
-
-    private void assertCannotSetPermissionGrantStateAppPreM(String permission, int value) throws Exception {
-        assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission,
-                value));
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-
-        // Install time permissions should always be granted
-        PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, 0);
-        assertEquals(PERMISSION_GRANTED,
-                checkPermission(permission, packageInfo.applicationInfo.uid,
-                        SIMPLE_PRE_M_APP_PACKAGE_NAME));
-    }
-
-    private void assertCanSetPermissionGrantStateAppPreM(String permission, int value) throws Exception {
         assertTrue(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission,
-                value));
+                PRE_M_APP_PACKAGE_NAME, permission, value));
         assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission),
-                value);
+                PRE_M_APP_PACKAGE_NAME, permission), value);
 
         // Install time permissions should always be granted
-        assertEquals(mPackageManager.checkPermission(permission,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME),
-                PackageManager.PERMISSION_GRANTED);
-
-        PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, 0);
+        PermissionUtils.checkPermission(permission, PERMISSION_GRANTED, PRE_M_APP_PACKAGE_NAME);
 
         // For pre-M apps the access to the data might be prevented via app-ops. Hence check that
         // they are correctly set
-        boolean isGranted = (checkPermission(permission, packageInfo.applicationInfo.uid,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME) == PERMISSION_GRANTED);
         switch (value) {
-            case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED:
-                assertTrue(isGranted);
+            case PERMISSION_GRANT_STATE_GRANTED:
+                PermissionUtils.checkPermissionAndAppOps(permission, PERMISSION_GRANTED,
+                        PRE_M_APP_PACKAGE_NAME);
                 break;
-            case DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED:
-                assertFalse(isGranted);
+            case PERMISSION_GRANT_STATE_DENIED:
+                PermissionUtils.checkPermissionAndAppOps(permission, PERMISSION_DENIED,
+                        PRE_M_APP_PACKAGE_NAME);
                 break;
             default:
                 fail("unsupported policy value");
         }
     }
 
-    private void pressPermissionPromptButton(String resName) throws Exception {
-        if (resName == null) {
-            return;
-        }
+    public void testPermissionPolicyAutoDeny() throws Exception {
+        setPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
 
-        BySelector selector = By
-                .clazz(android.widget.Button.class.getName())
-                .res("com.android.packageinstaller", resName);
-        mDevice.wait(Until.hasObject(selector), 5000);
-        UiObject2 button = mDevice.findObject(selector);
-        assertNotNull("Couldn't find button with resource id: " + resName, button);
-        button.click();
+        assertPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+        assertCannotRequestPermissionFromActivity(READ_CONTACTS);
     }
 
-    private class PermissionBroadcastReceiver extends BroadcastReceiver {
-        private BlockingQueue<Integer> mQueue = new ArrayBlockingQueue<Integer> (1);
+    public void testPermissionPolicyAutoDeny_permissionLocked() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        int permissionPolicy = mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DEFAULT);
+            testPermissionPolicyAutoDeny();
 
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            Integer result = new Integer(intent.getIntExtra(EXTRA_GRANT_STATE, PERMISSION_ERROR));
-            Log.d(TAG, "Grant state received " + result);
-            assertTrue(mQueue.add(result));
-        }
+            // Permission should be locked, so changing the policy should not change the grant state
+            setPermissionPolicy(PERMISSION_POLICY_PROMPT);
 
-        public int waitForBroadcast() throws Exception {
-            Integer result = mQueue.poll(30, TimeUnit.SECONDS);
-            mQueue.clear();
-            assertNotNull(result);
-            Log.d(TAG, "Grant state retrieved " + result.intValue());
-            return result.intValue();
+            assertPermissionPolicy(PERMISSION_POLICY_PROMPT);
+            assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+            assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
+            setPermissionPolicy(permissionPolicy);
         }
     }
 
-    private CountDownLatch initLocationPermissionNotificationLatch() {
+    public void testPermissionPolicyAutoGrant() throws Exception {
+        setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+        assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+        assertCanRequestPermissionFromActivity(READ_CONTACTS);
+    }
+
+    public void testPermissionPolicyAutoGrant_permissionLocked() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        int permissionPolicy = mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DEFAULT);
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+
+            // permission should be locked, so changing the policy should not change the grant state
+            setPermissionPolicy(PERMISSION_POLICY_PROMPT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_PROMPT);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
+            setPermissionPolicy(permissionPolicy);
+        }
+    }
+
+    public void testPermissionPolicyAutoGrant_multiplePermissionsInGroup() throws Exception {
+        setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+        // Both permissions should be granted
+        assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+        assertCanRequestPermissionFromActivity(READ_CONTACTS);
+        assertCanRequestPermissionFromActivity(WRITE_CONTACTS);
+    }
+
+    public void testCannotRequestPermission() throws Exception {
+        assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+    }
+
+    public void testCanRequestPermission() throws Exception {
+        assertCanRequestPermissionFromActivity(READ_CONTACTS);
+    }
+
+    @Suppress // Flakey.
+    public void testPermissionPrompts() throws Exception {
+        // register a crash watcher
+        mDevice.registerWatcher(CRASH_WATCHER_ID, () -> {
+            UiObject2 button = mDevice.findObject(CRASH_POPUP_BUTTON_SELECTOR);
+            if (button != null) {
+                UiObject2 text = mDevice.findObject(CRASH_POPUP_TEXT_SELECTOR);
+                Log.d(TAG, "Removing an error dialog: " + text != null ? text.getText() : null);
+                button.click();
+                return true;
+            }
+            return false;
+        });
+        mDevice.runWatchers();
+        setPermissionPolicy(PERMISSION_POLICY_PROMPT);
+
+        assertPermissionPolicy(PERMISSION_POLICY_PROMPT);
+        PermissionUtils.launchActivityAndRequestPermission(mReceiver, mDevice, READ_CONTACTS,
+                PERMISSION_DENIED, PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+        PermissionUtils.checkPermission(READ_CONTACTS, PERMISSION_DENIED,
+                PERMISSION_APP_PACKAGE_NAME);
+        PermissionUtils.launchActivityAndRequestPermission(mReceiver, mDevice, READ_CONTACTS,
+                PERMISSION_GRANTED, PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+        PermissionUtils.checkPermission(READ_CONTACTS, PERMISSION_GRANTED,
+                PERMISSION_APP_PACKAGE_NAME);
+    }
+
+    private CountDownLatch initPermissionNotificationLatch() {
         CountDownLatch notificationCounterLatch = new CountDownLatch(1);
         NotificationListener.getInstance().addListener((notification) -> {
             if (notification.getPackageName().equals(
-                    mPackageManager.getPermissionControllerPackageName()) &&
+                    mContext.getPackageManager().getPermissionControllerPackageName()) &&
                     notification.getNotification().getChannelId().equals(
                             AUTO_GRANTED_PERMISSIONS_CHANNEL_ID)) {
                 notificationCounterLatch.countDown();
@@ -501,4 +439,51 @@
         });
         return notificationCounterLatch;
     }
+
+    private void setPermissionPolicy(int permissionPolicy) {
+        mDevicePolicyManager.setPermissionPolicy(ADMIN_RECEIVER_COMPONENT, permissionPolicy);
+    }
+
+    private boolean setPermissionGrantState(String permission, int grantState) {
+        return mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, permission, grantState);
+    }
+
+    private void unableToSetPermissionGrantState(String permission, int grantState) {
+        assertFalse(setPermissionGrantState(permission, grantState));
+    }
+
+    private void assertPermissionGrantState(String permission, int grantState) {
+        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, permission), grantState);
+    }
+
+    private void assertPermissionPolicy(int permissionPolicy) {
+        assertEquals(mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT),
+                permissionPolicy);
+    }
+
+    private void assertCanRequestPermissionFromActivity(String permission) throws Exception {
+        PermissionUtils.launchActivityAndRequestPermission(
+                mReceiver, permission, PERMISSION_GRANTED,
+                PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+    }
+
+    private void assertCannotRequestPermissionFromActivity(String permission) throws Exception {
+        PermissionUtils.launchActivityAndRequestPermission(
+                mReceiver, permission, PERMISSION_DENIED,
+                PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+    }
+
+    private void assertHasPermissionFromActivity(String permission) throws Exception {
+        PermissionUtils.launchActivityAndCheckPermission(
+                mReceiver, permission, PERMISSION_GRANTED,
+                PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+    }
+
+    private void assertNoPermissionFromActivity(String permission) throws Exception {
+        PermissionUtils.launchActivityAndCheckPermission(
+                mReceiver, permission, PERMISSION_DENIED,
+                PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
index 8baf460..656210f 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
@@ -16,36 +16,147 @@
 
 package com.android.cts.deviceandprofileowner;
 
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
 import static org.testng.Assert.assertThrows;
 
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.PowerManager;
 import android.os.Process;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Test;
+
+import java.util.List;
 
 public class SecondaryLockscreenTest extends BaseDeviceAdminTest {
 
+    private static final int UI_AUTOMATOR_WAIT_TIME_MILLIS = 5000;
+    private static final String TAG = "SecondaryLockscreenTest";
+
+    private UiDevice mUiDevice;
+
     @Before
     @Override
     public void setUp() throws Exception {
         super.setUp();
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        runShellCommand("locksettings set-pin 1234");
+
+        mDevicePolicyManager.clearPackagePersistentPreferredActivities(ADMIN_RECEIVER_COMPONENT,
+                mContext.getPackageName());
+
         assertFalse(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
+        mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT, true);
+        assertTrue(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
     }
 
     @After
     @Override
     public void tearDown() throws Exception {
-        assertFalse(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
         super.tearDown();
+        mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT, false);
+        assertFalse(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
+        runShellCommand("locksettings clear --old 1234");
     }
 
-    public void testSetSecondaryLockscreen_notSupervisionApp_throwsSecurityException() {
-        // This API is only available to the configured supervision app, which is not possible to
-        // override as part of a CTS test, so just test that a security exception is thrown as
-        // expected even for the DO/PO.
+    public void testSetSecondaryLockscreenEnabled() throws Exception {
+        enterKeyguardPin();
+        assertTrue("Lockscreen title not shown",
+                mUiDevice.wait(Until.hasObject(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+
+        mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT, false);
+
+        // Verify that the lockscreen is dismissed after disabling the feature
+        assertFalse(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
+        verifyHomeLauncherIsShown();
+    }
+
+    public void testHomeButton() throws Exception {
+        enterKeyguardPin();
+        assertTrue("Lockscreen title not shown",
+                mUiDevice.wait(Until.hasObject(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+
+        // Verify that pressing home does not dismiss the lockscreen
+        mUiDevice.pressHome();
+        verifySecondaryLockscreenIsShown();
+    }
+
+    public void testDismiss() throws Exception {
+        enterKeyguardPin();
+        assertTrue("Lockscreen title not shown",
+                mUiDevice.wait(Until.hasObject(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+
+        mUiDevice.findObject(By.text(SimpleKeyguardService.DISMISS_BUTTON_LABEL)).click();
+        verifyHomeLauncherIsShown();
+
+        // Verify that the feature is not disabled after dismissal
+        enterKeyguardPin();
+        assertTrue(mUiDevice.wait(Until.hasObject(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                UI_AUTOMATOR_WAIT_TIME_MILLIS));
+        verifySecondaryLockscreenIsShown();
+    }
+
+    public void testSetSecondaryLockscreen_ineligibleAdmin_throwsSecurityException() {
+        final ComponentName badAdmin = new ComponentName("com.foo.bar", ".NonProfileOwnerReceiver");
         assertThrows(SecurityException.class,
-                () -> mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT,
-                        true));
+                () -> mDevicePolicyManager.setSecondaryLockscreenEnabled(badAdmin, true));
+    }
+
+    private void enterKeyguardPin() throws Exception {
+        final PowerManager pm = mContext.getSystemService(PowerManager.class);
+        runShellCommand("input keyevent KEYCODE_SLEEP");
+        waitUntil("Device still interactive", 5,
+                () -> pm != null && !pm.isInteractive());
+        runShellCommand("input keyevent KEYCODE_WAKEUP");
+        waitUntil("Device still not interactive", 5,
+                () -> pm.isInteractive());
+        runShellCommand("wm dismiss-keyguard");
+        mUiDevice.wait(Until.hasObject(By.res("com.android.systemui", "pinEntry")),
+                UI_AUTOMATOR_WAIT_TIME_MILLIS);
+        runShellCommand("input text 1234");
+        runShellCommand("input keyevent KEYCODE_ENTER");
+    }
+
+    private void verifyHomeLauncherIsShown() {
+        String launcherPackageName = getLauncherPackageName();
+        assertTrue("Lockscreen title is unexpectedly shown",
+                mUiDevice.wait(Until.gone(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+        assertTrue(String.format("Launcher (%s) is not shown", launcherPackageName),
+                mUiDevice.wait(Until.hasObject(By.pkg(launcherPackageName)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+    }
+
+    private void verifySecondaryLockscreenIsShown() {
+        String launcherPackageName = getLauncherPackageName();
+        assertTrue("Lockscreen title is unexpectedly not shown",
+                mUiDevice.wait(Until.hasObject(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+        assertTrue(String.format("Launcher (%s) is unexpectedly shown", launcherPackageName),
+                mUiDevice.wait(Until.gone(By.pkg(launcherPackageName)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+    }
+
+    private String getLauncherPackageName() {
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
+        List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentActivities(
+                homeIntent, 0);
+        StringBuilder sb = new StringBuilder();
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            sb.append(resolveInfo.activityInfo.packageName).append("/").append(
+                    resolveInfo.activityInfo.name).append(", ");
+        }
+        return resolveInfos.isEmpty() ? null : resolveInfos.get(0).activityInfo.packageName;
     }
 }
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java
new file mode 100644
index 0000000..0c81063
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import android.app.admin.DevicePolicyKeyguardService;
+import android.content.Context;
+import android.graphics.Color;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.widget.Button;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+/**
+ * Service that provides the secondary lockscreen content when the secondary lockscreen is enabled.
+ *
+ * <p>Handles the {@link DevicePolicyManager#ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE} action and
+ * in used in conjunction with {@link DevicePolicyManager.setSecondaryLockscreenEnabled}.
+ */
+public class SimpleKeyguardService extends DevicePolicyKeyguardService {
+
+    public static final String TITLE_LABEL = "SimpleKeyguardService Title";
+    public static final String DISMISS_BUTTON_LABEL = "Dismiss";
+    private static final String TAG = "SimpleKeyguardService";
+
+    @Override
+    public SurfaceControlViewHost.SurfacePackage onCreateKeyguardSurface(IBinder hostInputToken) {
+        Log.d(TAG, "onCreateKeyguardSurface()");
+        Context context = getApplicationContext();
+
+        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+
+        SurfaceControlViewHost wvr = new SurfaceControlViewHost(context, display, hostInputToken);
+        wvr.setView(createView(context), displayMetrics.widthPixels, displayMetrics.heightPixels);
+
+        return wvr.getSurfacePackage();
+    }
+
+    private View createView(Context context) {
+        TextView title = new TextView(context);
+        title.setText(TITLE_LABEL);
+
+        Button button = new Button(context);
+        button.setText(DISMISS_BUTTON_LABEL);
+        button.setOnClickListener(ignored -> {
+            button.setText("Dismissing...");
+            button.setEnabled(false);
+            dismiss();
+        });
+
+        RelativeLayout rootView = new RelativeLayout(context);
+        rootView.setBackgroundColor(Color.WHITE);
+        rootView.addView(title);
+        rootView.addView(button);
+        return rootView;
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
index 9bb371b..01d8572 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
@@ -20,26 +20,34 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.UiAutomation;
 import android.app.admin.DevicePolicyManager;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.hardware.camera2.CameraManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
+import com.android.cts.devicepolicy.CameraUtils;
+
 import com.google.common.collect.ImmutableSet;
 
-import java.util.concurrent.TimeUnit;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 public class UserRestrictionsParentTest extends InstrumentationTestCase {
 
     private static final String TAG = "UserRestrictionsParentTest";
 
     protected Context mContext;
+    private ContentResolver mContentResolver;
+    private UiAutomation mUiAutomation;
     private DevicePolicyManager mDevicePolicyManager;
     private UserManager mUserManager;
 
@@ -56,6 +64,8 @@
     protected void setUp() throws Exception {
         super.setUp();
         mContext = getInstrumentation().getContext();
+        mContentResolver = mContext.getContentResolver();
+        mUiAutomation = getInstrumentation().getUiAutomation();
 
         mDevicePolicyManager = (DevicePolicyManager)
                 mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
@@ -72,6 +82,7 @@
 
     @Override
     protected void tearDown() throws Exception {
+        mUiAutomation.dropShellPermissionIdentity();
         stopBackgroundThread();
         super.tearDown();
     }
@@ -161,8 +172,7 @@
         while (successToOpen != canOpen && retries > 0) {
             retries--;
             Thread.sleep(500);
-            successToOpen = CameraUtils
-                    .blockUntilOpenCamera(mCameraManager, mBackgroundHandler);
+            successToOpen = CameraUtils.blockUntilOpenCamera(mCameraManager, mBackgroundHandler);
         }
         assertEquals(String.format("Timed out waiting the value to change to %b (actual=%b)",
                 canOpen, successToOpen), canOpen, successToOpen);
@@ -192,11 +202,18 @@
                     // UserManager.DISALLOW_DEBUGGING_FEATURES
             );
 
-    public void testPerProfileUserRestriction_onParent() {
+    public void testPerProfileUserRestriction_onParent() throws Settings.SettingNotFoundException {
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.CREATE_USERS");
+
         DevicePolicyManager parentDevicePolicyManager =
                 mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
         assertNotNull(parentDevicePolicyManager);
 
+        int locationMode = Settings.Secure.getIntForUser(mContentResolver,
+                Settings.Secure.LOCATION_MODE, UserHandle.USER_SYSTEM);
+
         for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS) {
             try {
                 boolean hasRestrictionOnManagedProfile = mUserManager.hasUserRestriction(
@@ -214,6 +231,12 @@
                 assertThat(hasUserRestriction(restriction)).isFalse();
             }
         }
+
+        // Restore the location mode setting after adding and removing the
+        // DISALLOW_SHARE_LOCATION user restriction. This is because, modifying this user
+        // restriction causes the location mode setting to be turned off.
+        Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.LOCATION_MODE, locationMode,
+                UserHandle.USER_SYSTEM);
     }
 
     private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index 673e166..ba8e071 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -15,69 +15,68 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.deviceowner" >
+     package="com.android.cts.deviceowner">
 
     <uses-sdk android:minSdkVersion="20"/>
 
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
     <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <application
-        android:testOnly="true"
-        android:usesCleartextTraffic="true">>
+    <application android:testOnly="true"
+         android:usesCleartextTraffic="true">&gt;
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.deviceowner.BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceowner.BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <receiver
-            android:name="com.android.cts.deviceowner.CreateAndManageUserTest$TestProfileOwner"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.deviceowner.CreateAndManageUserTest$TestProfileOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <receiver
-                android:name="com.android.cts.deviceowner.CreateAndManageUserTest$SecondaryUserAdminReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.deviceowner.CreateAndManageUserTest$SecondaryUserAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <service android:name="com.android.cts.deviceowner.CreateAndManageUserTest$PrimaryUserService"
-                 android:exported="true"
-                 android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
         </service>
 
-        <activity
-            android:name=".SetPolicyActivity"
-            android:launchMode="singleTop">
+        <activity android:name=".SetPolicyActivity"
+             android:launchMode="singleTop"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
@@ -85,19 +84,19 @@
         <activity android:name="com.android.compatibility.common.util.devicepolicy.provisioning.StartProvisioningActivity"/>
 
         <service android:name="com.android.cts.deviceowner.NotificationListener"
-                 android:label="Notification Listener"
-                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+             android:label="Notification Listener"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.deviceowner"
-                     android:label="Device Owner CTS tests">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener"/>
+         android:targetPackage="com.android.cts.deviceowner"
+         android:label="Device Owner CTS tests">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/proxy/PacProxyTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/proxy/PacProxyTest.java
index ce5aa03..2e35bd4 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/proxy/PacProxyTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/proxy/PacProxyTest.java
@@ -78,6 +78,15 @@
       "}";
 
   /**
+   * PAC file that uses locale-specific string manipulations.
+   */
+  private static final String STRING_CASE_PAC = "function FindProxyForURL(url, host) {" +
+      "  if (\"hElLo\".toUpperCase() != \"HELLO\") return \"PROXY failed:8080\";" +
+      "  if (\"hElLo\".toLowerCase() != \"hello\") return \"PROXY failed:8080\";" +
+      "  return \"PROXY passed:8080\";" +
+      "}";
+
+  /**
    * Wait for the PacFileServer to tell us it has had a successful
    * HTTP request and responded with the PAC file we set.
    */
@@ -260,4 +269,22 @@
     assertEquals("Incorrect URL should return DIRECT",
         newArrayList(Proxy.NO_PROXY), list);
   }
+
+  /**
+   * Test a PAC file with toUpperCase/toLowerCase manipulations.
+   */
+  public void testStringCase() throws Exception {
+    mPacServer.setPacFile(STRING_CASE_PAC);
+    setPacURLAndWaitForDownload();
+
+    waitForSetProxySysProp();
+
+    URI uri = new URI("http://testhost/");
+
+    ProxySelector selector = ProxySelector.getDefault();
+    List<Proxy> list = selector.select(uri);
+    assertEquals("Correct URL returns proxy",
+        newArrayList(new Proxy(Type.HTTP, InetSocketAddress.createUnresolved("passed", 8080))),
+        list);
+  }
 }
diff --git a/hostsidetests/devicepolicy/app/DummyApps/dummyapp1/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DummyApps/dummyapp1/AndroidManifest.xml
index 3bec42a..4e8701c 100644
--- a/hostsidetests/devicepolicy/app/DummyApps/dummyapp1/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DummyApps/dummyapp1/AndroidManifest.xml
@@ -14,25 +14,25 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A dummy app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.dummyapps.dummyapp1">
+     package="com.android.cts.dummyapps.dummyapp1">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true"
-            android:crossProfile="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver">
+         android:crossProfile="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED" />
+                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="android.app.Activity"
-            android:exported="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DummyApps/dummyapp2/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DummyApps/dummyapp2/AndroidManifest.xml
index 1f1975f..fe1ee2b 100644
--- a/hostsidetests/devicepolicy/app/DummyApps/dummyapp2/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DummyApps/dummyapp2/AndroidManifest.xml
@@ -14,25 +14,25 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A dummy app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.dummyapps.dummyapp2">
+     package="com.android.cts.dummyapps.dummyapp2">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true"
-                 android:crossProfile="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver">
+         android:crossProfile="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED" />
+                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="android.app.Activity"
-            android:exported="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DummyApps/dummyapp3/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DummyApps/dummyapp3/AndroidManifest.xml
index 446c3a1..669a758 100644
--- a/hostsidetests/devicepolicy/app/DummyApps/dummyapp3/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DummyApps/dummyapp3/AndroidManifest.xml
@@ -14,25 +14,25 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A dummy app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.dummyapps.dummyapp3">
+     package="com.android.cts.dummyapps.dummyapp3">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true"
-                 android:crossProfile="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver">
+         android:crossProfile="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED" />
+                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="android.app.Activity"
-            android:exported="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DummyApps/dummyapp4/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DummyApps/dummyapp4/AndroidManifest.xml
index 8c0356c..78ad1b6 100644
--- a/hostsidetests/devicepolicy/app/DummyApps/dummyapp4/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DummyApps/dummyapp4/AndroidManifest.xml
@@ -14,25 +14,25 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A dummy app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.dummyapps.dummyapp4">
+     package="com.android.cts.dummyapps.dummyapp4">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true"
-                 android:crossProfile="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver">
+         android:crossProfile="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED" />
+                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="android.app.Activity"
-            android:exported="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DummyIme/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DummyIme/AndroidManifest.xml
index aae922a..7ac888f 100644
--- a/hostsidetests/devicepolicy/app/DummyIme/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DummyIme/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
  * Copyright 2020, The Android Open Source Project
@@ -17,27 +18,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.dummyime">
+     package="com.android.cts.dummyime">
     <application android:label="Dummy IME">
         <service android:name="DummyIme"
-                android:permission="android.permission.BIND_INPUT_METHOD">
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
-            <meta-data android:name="android.view.im" android:resource="@xml/method" />
+            <meta-data android:name="android.view.im"
+                 android:resource="@xml/method"/>
         </service>
-        <activity android:name=".ImePreferences" android:label="Dummy IME Settings">
+        <activity android:name=".ImePreferences"
+             android:label="Dummy IME Settings"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.dummyime">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.dummyime">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DummyLauncher/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DummyLauncher/AndroidManifest.xml
index a80e9b4..1195e28 100644
--- a/hostsidetests/devicepolicy/app/DummyLauncher/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DummyLauncher/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
  * Copyright 2020, The Android Open Source Project
@@ -17,9 +18,10 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.dummylauncher">
+     package="com.android.cts.dummylauncher">
     <application android:label="Dummy Launcher">
-        <activity android:name="android.app.Activity">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -27,14 +29,12 @@
             </intent-filter>
         </activity>
         <activity android:name=".QuietModeToggleActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.dummylauncher">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.dummylauncher">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/AndroidManifest.xml
index 760b31f..c1b179d 100755
--- a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/AndroidManifest.xml
@@ -16,17 +16,18 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.haslauncheractivityapp">
-    <uses-permission android:name="android.permission.INTERNET" />
+     package="com.android.cts.haslauncheractivityapp">
+    <uses-permission android:name="android.permission.INTERNET"/>
     <application android:testOnly="true">
-        <activity android:name="com.android.cts.haslauncheractivityapp.MainActivity">
+        <activity android:name="com.android.cts.haslauncheractivityapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <service android:name=".EmptyService" android:enabled="true"></service>
+        <service android:name=".EmptyService"
+             android:enabled="true"/>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/no_launcher_activity_AndroidManifest.xml b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/no_launcher_activity_AndroidManifest.xml
index ae2249a..614377c 100755
--- a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/no_launcher_activity_AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/no_launcher_activity_AndroidManifest.xml
@@ -19,7 +19,8 @@
     package="com.android.cts.nolauncheractivityapp">
     <uses-permission android:name="android.permission.INTERNET" />
     <application>
-        <activity android:name="com.android.cts.haslauncheractivityapp.MainActivity">
+        <activity android:name="com.android.cts.haslauncheractivityapp.MainActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
             </intent-filter>
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
index 22614fe..a5b4801 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
@@ -15,35 +15,37 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.intent.receiver">
+     package="com.android.cts.intent.receiver">
 
     <uses-sdk android:minSdkVersion="19"/>
 
     <uses-permission android:name="com.android.cts.managedprofile.permission.SAMPLE"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.cts.intent.receiver.IntentReceiverActivity">
+        <activity android:name="com.android.cts.intent.receiver.IntentReceiverActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.action.COPY_TO_CLIPBOARD" />
-                <action android:name="com.android.cts.action.READ_FROM_URI" />
-                <action android:name="com.android.cts.action.TAKE_PERSISTABLE_URI_PERMISSION" />
-                <action android:name="com.android.cts.action.WRITE_TO_URI" />
+                <action android:name="com.android.cts.action.COPY_TO_CLIPBOARD"/>
+                <action android:name="com.android.cts.action.READ_FROM_URI"/>
+                <action android:name="com.android.cts.action.TAKE_PERSISTABLE_URI_PERMISSION"/>
+                <action android:name="com.android.cts.action.WRITE_TO_URI"/>
                 <action android:name="com.android.cts.action.NOTIFY_URI_CHANGE"/>
                 <action android:name="com.android.cts.action.OBSERVE_URI_CHANGE"/>
-                <action android:name="com.android.cts.action.JUST_CREATE" />
-                <action android:name="com.android.cts.action.CREATE_AND_WAIT" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.action.JUST_CREATE"/>
+                <action android:name="com.android.cts.action.CREATE_AND_WAIT"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <activity android:name=".SimpleIntentReceiverActivity" android:exported="true"/>
+        <activity android:name=".SimpleIntentReceiverActivity"
+             android:exported="true"/>
 
         <activity-alias android:name=".BrowserActivity"
-            android:targetActivity=".SimpleIntentReceiverActivity">
+             android:targetActivity=".SimpleIntentReceiverActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW"/>
                 <category android:name="android.intent.category.DEFAULT"/>
@@ -53,16 +55,19 @@
         </activity-alias>
 
         <activity-alias android:name=".AppLinkActivity"
-            android:targetActivity=".SimpleIntentReceiverActivity">
+             android:targetActivity=".SimpleIntentReceiverActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW"/>
                 <category android:name="android.intent.category.DEFAULT"/>
                 <category android:name="android.intent.category.BROWSABLE"/>
-                <data android:scheme="http" android:host="com.android.cts.intent.receiver"/>
+                <data android:scheme="http"
+                     android:host="com.android.cts.intent.receiver"/>
             </intent-filter>
         </activity-alias>
 
-        <receiver android:name=".BroadcastIntentReceiver">
+        <receiver android:name=".BroadcastIntentReceiver"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_OWNER_CHANGED"/>
             </intent-filter>
@@ -70,9 +75,8 @@
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.intent.receiver"
-        android:label="Intent Receiver CTS Tests" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.intent.receiver"
+         android:label="Intent Receiver CTS Tests"/>
 
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml b/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml
index 730250b..4953096 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml
@@ -15,43 +15,39 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.intent.sender">
+     package="com.android.cts.intent.sender">
 
-    <permission
-        android:name="com.android.cts.intent.sender.permission.SAMPLE"
-        android:label="Sample Permission" />
+    <permission android:name="com.android.cts.intent.sender.permission.SAMPLE"
+         android:label="Sample Permission"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name=".IntentSenderActivity">
+        <activity android:name=".IntentSenderActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <provider
-            android:name="androidx.core.content.FileProvider"
-            android:authorities="com.android.cts.intent.sender.fileprovider"
-            android:grantUriPermissions="true"
-            android:exported="false">
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/filepaths" />
+        <provider android:name="androidx.core.content.FileProvider"
+             android:authorities="com.android.cts.intent.sender.fileprovider"
+             android:grantUriPermissions="true"
+             android:exported="false">
+            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+                 android:resource="@xml/filepaths"/>
         </provider>
 
-        <provider
-            android:name=".BasicContentProvider"
-            android:authorities="com.android.cts.intent.sender.provider"
-            android:grantUriPermissions="true"
-            android:exported="true"
-            android:permission="com.android.cts.intent.sender.permission.SAMPLE" />
+        <provider android:name=".BasicContentProvider"
+             android:authorities="com.android.cts.intent.sender.provider"
+             android:grantUriPermissions="true"
+             android:exported="true"
+             android:permission="com.android.cts.intent.sender.permission.SAMPLE"/>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.intent.sender"
-        android:label="Intent Sender CTS Tests" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.intent.sender"
+         android:label="Intent Sender CTS Tests"/>
 
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java
index 21e3991..f205874 100644
--- a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java
@@ -21,6 +21,7 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertThrows;
@@ -30,7 +31,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.support.test.uiautomator.UiDevice;
@@ -46,8 +46,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+import java.util.Set;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * Test that runs {@link UserManager#trySetQuietModeEnabled(boolean, UserHandle)} API
@@ -147,7 +152,7 @@
         assertNotNull("Failed to receive ACTION_MANAGED_PROFILE_UNAVAILABLE broadcast", intent);
         assertTrue(mUserManager.isQuietModeEnabled(mTargetUser));
 
-        waitForUserNotRunning(30);
+        waitForUserLocked();
 
         intent = trySetQuietModeEnabled(false,
                 UserManager.QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED, false);
@@ -155,15 +160,24 @@
         assertTrue(mUserManager.isQuietModeEnabled(mTargetUser));
     }
 
-    // Note: The timeout is in seconds.
-    private void waitForUserNotRunning(int timeout) {
-        for(int i = 0 ; i < 2 * timeout ; i++) {
-            if (!mUserManager.isUserRunning(mTargetUser)) {
-                break;
+    private void waitForUserLocked() throws Exception {
+        // Should match a line in "dumpsys mount" output like this:
+        // Local unlocked users: [0, 10]
+        final Pattern p = Pattern.compile("Local unlocked users: \\[(.*)\\]");
+        final long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(60);
+        while (System.nanoTime() < deadline) {
+            final String output = mUiDevice.executeShellCommand("dumpsys mount");
+            final Matcher matcher = p.matcher(output);
+            assertTrue("Unexpected dupmsys mount output: " + output, matcher.find());
+            final Set<Integer> unlockedUsers = Arrays.stream(matcher.group(1).split(", "))
+                    .map(Integer::valueOf)
+                    .collect(Collectors.toSet());
+            if (!unlockedUsers.contains(mTargetUser.getIdentifier())) {
+                return;
             }
-            SystemClock.sleep(500);
+            Thread.sleep(500);
         }
-        assertFalse("Cannot get the profile stopped.", mUserManager.isUserRunning(mTargetUser));
+        fail("Cannot get the profile locked");
     }
 
     private Intent trySetQuietModeEnabled(boolean enabled, int flags,
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
index 14abd1a..ae9b27e 100644
--- a/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
@@ -15,19 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.launchertests.support">
+     package="com.android.cts.launchertests.support">
 
     <!-- Target 25.  Don't change to >= 26 since that'll break background services. -->
-    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25"/>
+    <uses-sdk android:minSdkVersion="21"
+         android:targetSdkVersion="25"/>
 
     <application>
-        <service android:name=".LauncherCallbackTestsService" >
+        <service android:name=".LauncherCallbackTestsService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.launchertests.support.REGISTER_CALLBACK" />
+                <action android:name="com.android.cts.launchertests.support.REGISTER_CALLBACK"/>
             </intent-filter>
         </service>
 
-        <activity android:name=".LauncherActivity">
+        <activity android:name=".LauncherActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -35,7 +38,8 @@
             </intent-filter>
         </activity>
 
-        <receiver android:name=".QuietModeCommandReceiver" android:exported="true">
+        <receiver android:name=".QuietModeCommandReceiver"
+             android:exported="true">
             <intent-filter>
                 <action android:name="toggle_quiet_mode"/>
             </intent-filter>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp b/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
index 565c88c..1c94ab3 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
@@ -30,6 +30,7 @@
         "truth-prebuilt",
         "testng",
         "androidx.legacy_legacy-support-v4",
+        "devicepolicy-deviceside-common",
     ],
     min_sdk_version: "27",
     // tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index e5786f6..99d440d 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -15,148 +15,155 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.managedprofile">
+     package="com.android.cts.managedprofile">
 
     <uses-sdk android:minSdkVersion="27"/>
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.CALL_PHONE"/>
     <uses-permission android:name="android.permission.READ_CALL_LOG"/>
     <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
-    <uses-permission android:name="android.permission.READ_CALENDAR" />
-    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    <uses-permission android:name="android.permission.READ_CALENDAR"/>
+    <uses-permission android:name="android.permission.WRITE_CALENDAR"/>
     <uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY"/>
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.managedprofile.BaseManagedProfileTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.managedprofile.BaseManagedProfileTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <receiver
-                android:name="com.android.cts.managedprofile.ProvisioningTest$ProvisioningAdminReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.managedprofile.ProvisioningTest$ProvisioningAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <receiver
-            android:name="com.android.cts.managedprofile.PrimaryUserDeviceAdmin"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.managedprofile.PrimaryUserDeviceAdmin"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/primary_device_admin" />
+                 android:resource="@xml/primary_device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <activity android:name=".PrimaryUserFilterSetterActivity">
+        <activity android:name=".PrimaryUserFilterSetterActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEFAULT"/>
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".ComponentDisablingActivity" android:exported="true">
+        <activity android:name=".ComponentDisablingActivity"
+             android:exported="true">
         </activity>
-        <activity android:name=".ManagedProfileActivity">
+        <activity android:name=".ManagedProfileActivity"
+             android:exported="true">
             <intent-filter>
                 <category android:name="android.intent.category.DEFAULT"/>
-                <action android:name="com.android.cts.managedprofile.ACTION_TEST_MANAGED_ACTIVITY" />
+                <action android:name="com.android.cts.managedprofile.ACTION_TEST_MANAGED_ACTIVITY"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SEND_MULTIPLE" />
-                <data android:mimeType="*/*" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SEND_MULTIPLE"/>
+                <data android:mimeType="*/*"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity android:name=".PrimaryUserActivity">
+        <activity android:name=".PrimaryUserActivity"
+             android:exported="true">
             <intent-filter>
                 <category android:name="android.intent.category.DEFAULT"/>
-                <action android:name="com.android.cts.managedprofile.ACTION_TEST_PRIMARY_ACTIVITY" />
+                <action android:name="com.android.cts.managedprofile.ACTION_TEST_PRIMARY_ACTIVITY"/>
             </intent-filter>
             <!-- Catch ACTION_PICK in case there is no other app handing it -->
             <intent-filter>
-                <action android:name="android.intent.action.PICK" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-        <activity android:name=".AllUsersActivity">
-            <intent-filter>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <action android:name="com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name=".SetPolicyActivity"
-            android:launchMode="singleTop">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.PICK"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity android:name=".TestActivity" />
+        <activity android:name=".AllUsersActivity"
+             android:exported="true">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <action android:name="com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".SetPolicyActivity"
+             android:launchMode="singleTop"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".TestActivity"/>
 
         <service android:name=".DummyConnectionService"
-                 android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
-        <activity android:name=".DummyDialerActivity">
+        <activity android:name=".DummyDialerActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
         </activity>
-        <service android:name=".AccountService" android:exported="true">
+        <service android:name=".AccountService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
             <meta-data android:name="android.accounts.AccountAuthenticator"
-                       android:resource="@xml/authenticator" />
+                 android:resource="@xml/authenticator"/>
         </service>
         <activity android:name="com.android.compatibility.common.util.devicepolicy.provisioning.StartProvisioningActivity"/>
 
-        <activity
-                android:name=".ProvisioningSuccessActivity"
-                android:theme="@android:style/Theme.NoDisplay">
+        <activity android:name=".ProvisioningSuccessActivity"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.PROVISIONING_SUCCESSFUL"/>
                 <category android:name="android.intent.category.DEFAULT"/>
@@ -164,68 +171,72 @@
         </activity>
 
         <activity android:name=".WebViewActivity"
-            android:process=":testProcess"/>
+             android:process=":testProcess"/>
 
-        <activity android:name=".TimeoutActivity" android:exported="true"/>
+        <activity android:name=".TimeoutActivity"
+             android:exported="true"/>
 
-        <activity
-            android:name=".DummyCrossProfileViewEventActivity"
-            android:exported="true">
+        <activity android:name=".DummyCrossProfileViewEventActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.provider.calendar.action.VIEW_MANAGED_PROFILE_CALENDAR_EVENT"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name=".CrossProfileNotificationListenerService"
-            android:label="CrossProfileNotificationListenerService"
-            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
+        <service android:name=".CrossProfileNotificationListenerService"
+             android:label="CrossProfileNotificationListenerService"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
 
-        <receiver android:name=".MissedCallNotificationReceiver">
+        <receiver android:name=".MissedCallNotificationReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
+                <action android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION"/>
             </intent-filter>
         </receiver>
 
         <!-- Dummy receiver that's decleared direct boot aware. This is needed to make the test app
-             executable by instrumentation before device unlock -->
+                         executable by instrumentation before device unlock -->
         <receiver android:name=".ResetPasswordWithTokenTest$DummyReceiver"
-          android:directBootAware="true" >
+             android:directBootAware="true"
+             android:exported="true">
           <intent-filter>
-            <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+            <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
           </intent-filter>
         </receiver>
 
-        <receiver android:name=".LockProfileReceiver">
+        <receiver android:name=".LockProfileReceiver"
+             android:exported="true">
           <intent-filter>
-            <action android:name="com.android.cts.managedprofile.LOCK_PROFILE" />
+            <action android:name="com.android.cts.managedprofile.LOCK_PROFILE"/>
           </intent-filter>
         </receiver>
 
-        <receiver android:name=".WipeDataReceiver">
+        <receiver android:name=".WipeDataReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.managedprofile.WIPE_DATA" />
-                <action android:name="com.android.cts.managedprofile.WIPE_DATA_WITH_REASON" />
+                <action android:name="com.android.cts.managedprofile.WIPE_DATA"/>
+                <action android:name="com.android.cts.managedprofile.WIPE_DATA_WITH_REASON"/>
             </intent-filter>
         </receiver>
 
         <service android:name=".NotificationListener"
-            android:exported="true"
-            android:label="Notification Listener"
-            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+             android:exported="true"
+             android:label="Notification Listener"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.managedprofile"
-                     android:label="Managed Profile CTS Tests"/>
+         android:targetPackage="com.android.cts.managedprofile"
+         android:label="Managed Profile CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java
index 6a57e2e..e3523e3 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java
@@ -24,6 +24,8 @@
 import android.os.HandlerThread;
 import android.test.AndroidTestCase;
 
+import com.android.cts.devicepolicy.CameraUtils;
+
 
 public class CameraPolicyTest extends AndroidTestCase {
 
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraUtils.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraUtils.java
deleted file mode 100644
index 516e244..0000000
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraUtils.java
+++ /dev/null
@@ -1,93 +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.cts.managedprofile;
-
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.os.Handler;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A util class to help open camera in a blocking way.
- */
-class CameraUtils {
-
-    private static final String TAG = "CameraUtils";
-
-    /**
-     * @return true if success to open camera, false otherwise.
-     */
-    public static boolean blockUntilOpenCamera(CameraManager cameraManager, Handler handler) {
-        try {
-            String[] cameraIdList = cameraManager.getCameraIdList();
-            if (cameraIdList == null || cameraIdList.length == 0) {
-                return false;
-            }
-            String cameraId = cameraIdList[0];
-            CameraCallback callback = new CameraCallback();
-            cameraManager.openCamera(cameraId, callback, handler);
-            return callback.waitForResult();
-        } catch (Exception ex) {
-            // No matter what is going wrong, it means fail to open camera.
-            ex.printStackTrace();
-            return false;
-        }
-    }
-
-    /**
-     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
-     */
-    private static class CameraCallback extends CameraDevice.StateCallback {
-
-        private static final int OPEN_TIMEOUT_SECONDS = 5;
-
-        private final CountDownLatch mLatch = new CountDownLatch(1);
-
-        private AtomicBoolean mResult = new AtomicBoolean(false);
-
-        @Override
-        public void onOpened(CameraDevice cameraDevice) {
-            Log.d(TAG, "open camera successfully");
-            mResult.set(true);
-            if (cameraDevice != null) {
-                cameraDevice.close();
-            }
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onDisconnected(CameraDevice cameraDevice) {
-            Log.d(TAG, "disconnect camera");
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onError(CameraDevice cameraDevice, int error) {
-            Log.e(TAG, "Fail to open camera, error code = " + error);
-            mLatch.countDown();
-        }
-
-        public boolean waitForResult() throws InterruptedException {
-            mLatch.await(OPEN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
-            return mResult.get();
-        }
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/MeteredDataTestApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/MeteredDataTestApp/AndroidManifest.xml
index d1228f8..5094f4e 100644
--- a/hostsidetests/devicepolicy/app/MeteredDataTestApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/MeteredDataTestApp/AndroidManifest.xml
@@ -16,17 +16,18 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.devicepolicy.metereddatatestapp">
+     package="com.android.cts.devicepolicy.metereddatatestapp">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
 
     <application>
-        <activity android:name=".MainActivity" >
+        <activity android:name=".MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml b/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml
index d81cd43..32a4303 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml
@@ -15,32 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.packageinstaller">
+     package="com.android.cts.packageinstaller">
 
     <uses-sdk android:minSdkVersion="21"/>
 
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <receiver
-            android:name=".ClearDeviceOwnerTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name=".ClearDeviceOwnerTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.packageinstaller"
-        android:label="Package Installer CTS Tests" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.packageinstaller"
+         android:label="Package Installer CTS Tests"/>
 
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ProfileOwner/AndroidManifest.xml
index a494ed6..bab39b3 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/AndroidManifest.xml
@@ -15,28 +15,27 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.profileowner" >
+     package="com.android.cts.profileowner">
 
     <uses-sdk android:minSdkVersion="24"/>
 
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.profileowner.BaseProfileOwnerTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.profileowner.BaseProfileOwnerTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.profileowner"
-                     android:label="Profile Owner CTS tests"/>
+         android:targetPackage="com.android.cts.profileowner"
+         android:label="Profile Owner CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/AndroidManifest.xml
index e6c7f42..b5b17c8 100644
--- a/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/AndroidManifest.xml
@@ -14,25 +14,26 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A dummy app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.sharingapps.sharingapp1">
+     package="com.android.cts.sharingapps.sharingapp1">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name=".SimpleActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="*/*" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="*/*"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/AndroidManifest.xml
index dbd3be3..b7ee819 100644
--- a/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/AndroidManifest.xml
@@ -14,25 +14,26 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A dummy app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.sharingapps.sharingapp2">
+     package="com.android.cts.sharingapps.sharingapp2">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name=".SimpleActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="*/*" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="*/*"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
index a25a1ee..d79c22c 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -16,83 +16,98 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.launcherapps.simpleapp">
+     package="com.android.cts.launcherapps.simpleapp">
 
     <uses-permission android:name="android.permission.READ_CALENDAR"/>
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
     <application>
-        <activity android:name=".SimpleActivity" >
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".NonExportedActivity">
-            android:exported="false">
+                        android:exported=&quot;false&quot;&gt;
         </activity>
         <activity android:name=".NonLauncherActivity">
-            android:exported="true">
+                        android:exported=&quot;true&quot;&gt;
         </activity>
         <activity android:name=".SimpleActivityStartService"
-            android:turnScreenOn="true"
-            android:excludeFromRecents="true"
-            android:exported="true" />
-        <activity android:name=".SimpleActivityStartFgService" android:exported="true" />
-        <activity android:name=".SimpleActivityImmediateExit" >
+             android:turnScreenOn="true"
+             android:excludeFromRecents="true"
+             android:exported="true"/>
+        <activity android:name=".SimpleActivityStartFgService"
+             android:exported="true"/>
+        <activity android:name=".SimpleActivityImmediateExit"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".SimpleActivityChainExit" >
+        <activity android:name=".SimpleActivityChainExit"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <service android:name=".SimpleService" android:exported="true">
+        <service android:name=".SimpleService"
+             android:exported="true">
         </service>
-        <service android:name=".SimpleService2" android:exported="true" android:process=":other">
+        <service android:name=".SimpleService2"
+             android:exported="true"
+             android:process=":other">
         </service>
-        <service android:name=".SimpleService3" android:exported="true" />
+        <service android:name=".SimpleService3"
+             android:exported="true"/>
 
-        <service android:name=".SimpleService4" android:exported="true">
+        <service android:name=".SimpleService4"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <service android:name=".SimpleService5" android:exported="true" android:process=":remote">
+        <service android:name=".SimpleService5"
+             android:exported="true"
+             android:process=":remote">
             <intent-filter>
-                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <service android:name=".SimpleService6" android:exported="true"
-                android:isolatedProcess="true">
+        <service android:name=".SimpleService6"
+             android:exported="true"
+             android:isolatedProcess="true">
             <intent-filter>
-                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <receiver android:name=".SimpleReceiverStartService" android:exported="true">
+        <receiver android:name=".SimpleReceiverStartService"
+             android:exported="true">
         </receiver>
-        <receiver android:name=".SimpleReceiver" android:exported="true">
+        <receiver android:name=".SimpleReceiver"
+             android:exported="true">
         </receiver>
-        <receiver android:name=".SimpleRemoteReceiver" android:process=":receiver"
-                  android:exported="true">
+        <receiver android:name=".SimpleRemoteReceiver"
+             android:process=":receiver"
+             android:exported="true">
         </receiver>
-        <provider android:name=".SimpleProvider" android:process=":remote"
-                  android:authorities="com.android.cts.launcherapps.simpleapp.provider"
-                  android:exported="false" >
+        <provider android:name=".SimpleProvider"
+             android:process=":remote"
+             android:authorities="com.android.cts.launcherapps.simpleapp.provider"
+             android:exported="false">
         </provider>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/devicepolicy/app/SimplePreMApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimplePreMApp/AndroidManifest.xml
index 85962a1..e111a1d 100644
--- a/hostsidetests/devicepolicy/app/SimplePreMApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimplePreMApp/AndroidManifest.xml
@@ -16,20 +16,20 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.launcherapps.simplepremapp">
+     package="com.android.cts.launcherapps.simplepremapp">
 
     <uses-sdk android:targetSdkVersion="21"/>
 
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
 
     <application>
-        <activity android:name=".SimpleActivity" >
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/devicepolicy/app/SimpleSmsApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleSmsApp/AndroidManifest.xml
index 7c38b6d..782b25e 100644
--- a/hostsidetests/devicepolicy/app/SimpleSmsApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleSmsApp/AndroidManifest.xml
@@ -16,65 +16,65 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telephony.cts.sms.simplesmsapp">
+     package="android.telephony.cts.sms.simplesmsapp">
 
     <uses-permission android:name="android.permission.READ_SMS"/>
 
     <application android:label="SimpleSmsApp">
         <!-- BroadcastReceiver that listens for incoming SMS messages -->
         <receiver android:name="android.telephony.cts.sms.SmsReceiver"
-                  android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <!-- BroadcastReceiver that listens for incoming MMS messages -->
         <receiver android:name="android.telephony.cts.sms.MmsReceiver"
-                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
         </receiver>
 
         <!-- Activity that allows the user to send new SMS/MMS messages -->
         <activity android:name="android.app.Activity"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </activity>
 
-        <!-- Service that delivers messages from the phone "quick response" -->
+        <!-- Service that delivers messages from the phone "quick response"
+             -->
         <service android:name="android.telephony.cts.sms.HeadlessSmsSendService"
-                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
-                 android:exported="true" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.telephony.cts.sms.simplesmsapp">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.telephony.cts.sms.simplesmsapp">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/hostsidetests/devicepolicy/app/SingleAdminApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SingleAdminApp/AndroidManifest.xml
index daf7862..c35bef1 100644
--- a/hostsidetests/devicepolicy/app/SingleAdminApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SingleAdminApp/AndroidManifest.xml
@@ -15,20 +15,19 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.singleadmin">
+     package="com.android.cts.devicepolicy.singleadmin">
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <receiver
-            android:name="com.android.cts.devicepolicy.singleadmin.ProvisioningSingleAdminTest$AdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.devicepolicy.singleadmin.ProvisioningSingleAdminTest$AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
@@ -37,6 +36,6 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.devicepolicy.singleadmin"
-        android:label="Managed Profile CTS Tests (Single admin receiver)"/>
+         android:targetPackage="com.android.cts.devicepolicy.singleadmin"
+         android:label="Managed Profile CTS Tests (Single admin receiver)"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
index b3a8460..018f51b 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
@@ -15,46 +15,44 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.transferownerincoming">
+     package="com.android.cts.transferownerincoming">
 
     <uses-sdk android:minSdkVersion="24"/>
 
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
         <uses-library android:name="android.test.runner"/>
-        <receiver
-            android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin"/>
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <receiver
-            android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiverNoMetadata"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiverNoMetadata"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin_no_support_transfer_policy"/>
+                 android:resource="@xml/device_admin_no_support_transfer_policy"/>
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <service
-            android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminService"
-            android:exported="true"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <service android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminService"
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.transferownerincoming"
-                     android:label="Transfer Owner CTS tests"/>
+         android:targetPackage="com.android.cts.transferownerincoming"
+         android:label="Transfer Owner CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml
index e1a6dbb..6a0544d 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml
@@ -15,21 +15,20 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.transferowneroutgoing">
+     package="com.android.cts.transferowneroutgoing">
 
     <uses-sdk android:minSdkVersion="24"/>
 
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
         <uses-library android:name="android.test.runner"/>
-        <receiver
-            android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferOutgoingTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferOutgoingTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin"/>
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
@@ -37,6 +36,6 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.transferowneroutgoing"
-                     android:label="Transfer Owner CTS tests"/>
+         android:targetPackage="com.android.cts.transferowneroutgoing"
+         android:label="Transfer Owner CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/WidgetProvider/AndroidManifest.xml b/hostsidetests/devicepolicy/app/WidgetProvider/AndroidManifest.xml
index 77246b5..9a8f682 100644
--- a/hostsidetests/devicepolicy/app/WidgetProvider/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/WidgetProvider/AndroidManifest.xml
@@ -16,24 +16,25 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.widgetprovider">
+     package="com.android.cts.widgetprovider">
 
     <uses-permission android:name="android.permission.BIND_APPWIDGET"/>
 
     <application>
-        <receiver android:name="SimpleWidgetProvider" >
+        <receiver android:name="SimpleWidgetProvider"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
             </intent-filter>
             <meta-data android:name="android.appwidget.provider"
-                       android:resource="@xml/simple_widget_provider_info" />
+                 android:resource="@xml/simple_widget_provider_info"/>
         </receiver>
-        <service android:name=".SimpleAppWidgetHostService" >
+        <service android:name=".SimpleAppWidgetHostService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.widgetprovider.REGISTER_CALLBACK" />
+                <action android:name="com.android.cts.widgetprovider.REGISTER_CALLBACK"/>
             </intent-filter>
         </service>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/devicepolicy/app/common/Android.bp b/hostsidetests/devicepolicy/app/common/Android.bp
new file mode 100644
index 0000000..349a7e7
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Build the common library for use device-side
+java_library {
+    name: "devicepolicy-deviceside-common",
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+    libs: ["junit"],
+    static_libs: [
+            "androidx.legacy_legacy-support-v4",
+            "ctstestrunner-axt",
+            "androidx.test.rules",
+            "ub-uiautomator",
+            ],
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/CameraUtils.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/CameraUtils.java
new file mode 100644
index 0000000..61d0105
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/CameraUtils.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.util.Log;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A util class to help open camera in a blocking way.
+ */
+public class CameraUtils {
+
+    private static final String TAG = "CameraUtils";
+
+    /**
+     * @return true if success to open camera, false otherwise.
+     */
+    public static boolean blockUntilOpenCamera(CameraManager cameraManager, Handler handler) {
+        try {
+            String[] cameraIdList = cameraManager.getCameraIdList();
+            if (cameraIdList == null || cameraIdList.length == 0) {
+                return false;
+            }
+            String cameraId = cameraIdList[0];
+            CameraCallback callback = new CameraCallback();
+            cameraManager.openCamera(cameraId, callback, handler);
+            return callback.waitForResult();
+        } catch (Exception ex) {
+            // No matter what is going wrong, it means fail to open camera.
+            ex.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
+     */
+    private static class CameraCallback extends CameraDevice.StateCallback {
+
+        private static final int OPEN_TIMEOUT_SECONDS = 5;
+
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+
+        private AtomicBoolean mResult = new AtomicBoolean(false);
+
+        @Override
+        public void onOpened(CameraDevice cameraDevice) {
+            Log.d(TAG, "open camera successfully");
+            mResult.set(true);
+            if (cameraDevice != null) {
+                cameraDevice.close();
+            }
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onDisconnected(CameraDevice cameraDevice) {
+            Log.d(TAG, "disconnect camera");
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onError(CameraDevice cameraDevice, int error) {
+            Log.e(TAG, "Fail to open camera, error code = " + error);
+            mLatch.countDown();
+        }
+
+        public boolean waitForResult() throws InterruptedException {
+            mLatch.await(OPEN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            return mResult.get();
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionBroadcastReceiver.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionBroadcastReceiver.java
new file mode 100644
index 0000000..042ae0c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionBroadcastReceiver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class PermissionBroadcastReceiver extends BroadcastReceiver {
+    private static final String TAG = "PermissionBroadcastReceiver";
+
+    private static final String EXTRA_GRANT_STATE
+            = "com.android.cts.permission.extra.GRANT_STATE";
+    private static final int PERMISSION_ERROR = -2;
+
+    private BlockingQueue<Integer> mResultsQueue;
+
+    public PermissionBroadcastReceiver() {
+        mResultsQueue = new ArrayBlockingQueue<>(1);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Integer result = intent.getIntExtra(EXTRA_GRANT_STATE, PERMISSION_ERROR);
+        Log.d(TAG, "Grant state received " + result);
+        assertTrue(mResultsQueue.add(result));
+    }
+
+    public int waitForBroadcast() throws Exception {
+        Integer result = mResultsQueue.poll(30, TimeUnit.SECONDS);
+        mResultsQueue.clear();
+        assertNotNull("Expected broadcast to be received within 30 seconds but did not get it",
+                result);
+        Log.d(TAG, "Grant state retrieved " + result);
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
new file mode 100644
index 0000000..044e356
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+public class PermissionUtils {
+
+    private static final String ACTION_CHECK_HAS_PERMISSION
+            = "com.android.cts.permission.action.CHECK_HAS_PERMISSION";
+    private static final String ACTION_REQUEST_PERMISSION
+            = "com.android.cts.permission.action.REQUEST_PERMISSION";
+    private static final String EXTRA_PERMISSION = "com.android.cts.permission.extra.PERMISSION";
+
+    public static void launchActivityAndCheckPermission(PermissionBroadcastReceiver receiver,
+            String permission, int expected, String packageName, String activityName)
+            throws Exception {
+        launchActivityWithAction(permission, ACTION_CHECK_HAS_PERMISSION,
+                packageName, activityName);
+        assertEquals(expected, receiver.waitForBroadcast());
+    }
+
+    public static void launchActivityAndRequestPermission(PermissionBroadcastReceiver receiver,
+            String permission, int expected, String packageName, String activityName)
+            throws Exception {
+        launchActivityWithAction(permission, ACTION_REQUEST_PERMISSION,
+                packageName, activityName);
+        assertEquals(expected, receiver.waitForBroadcast());
+    }
+
+    public static void launchActivityAndRequestPermission(PermissionBroadcastReceiver
+            receiver, UiDevice device, String permission, int expected,
+            String packageName, String activityName) throws Exception {
+        final String resName;
+        switch(expected) {
+            case PERMISSION_DENIED:
+                resName = "permission_deny_button";
+                break;
+            case PERMISSION_GRANTED:
+                resName = "permission_allow_button";
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid expected permission");
+        }
+        launchActivityWithAction(permission, ACTION_REQUEST_PERMISSION,
+                packageName, activityName);
+        pressPermissionPromptButton(device, resName);
+        assertEquals(expected, receiver.waitForBroadcast());
+    }
+
+    private static void launchActivityWithAction(String permission, String action,
+            String packageName, String activityName) {
+        Intent launchIntent = new Intent();
+        launchIntent.setComponent(new ComponentName(packageName, activityName));
+        launchIntent.putExtra(EXTRA_PERMISSION, permission);
+        launchIntent.setAction(action);
+        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        getContext().startActivity(launchIntent);
+    }
+
+    public static void checkPermission(String permission, int expected, String packageName) {
+        assertEquals(getContext().getPackageManager()
+                .checkPermission(permission, packageName), expected);
+    }
+
+    /**
+     * Correctly check a runtime permission. This also works for pre-m apps.
+     */
+    public static void checkPermissionAndAppOps(String permission, int expected, String packageName)
+            throws Exception {
+        assertEquals(checkPermissionAndAppOps(permission, packageName), expected);
+    }
+
+    private static int checkPermissionAndAppOps(String permission, String packageName)
+            throws Exception {
+        PackageInfo packageInfo = getContext().getPackageManager().getPackageInfo(packageName, 0);
+        if (getContext().checkPermission(permission, -1, packageInfo.applicationInfo.uid)
+                == PERMISSION_DENIED) {
+            return PERMISSION_DENIED;
+        }
+
+        AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+        if (appOpsManager != null && appOpsManager.noteProxyOpNoThrow(
+                AppOpsManager.permissionToOp(permission), packageName,
+                packageInfo.applicationInfo.uid, null, null)
+                != AppOpsManager.MODE_ALLOWED) {
+            return PERMISSION_DENIED;
+        }
+
+        return PERMISSION_GRANTED;
+    }
+
+    public static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    private static void pressPermissionPromptButton(UiDevice mDevice, String resName) {
+        if (resName == null) {
+            throw new IllegalArgumentException("resName must not be null");
+        }
+
+        BySelector selector = By
+                .clazz(android.widget.Button.class.getName())
+                .res("com.android.packageinstaller", resName);
+        mDevice.wait(Until.hasObject(selector), 5000);
+        UiObject2 button = mDevice.findObject(selector);
+        assertNotNull("Couldn't find button with resource id: " + resName, button);
+        button.click();
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
index cd19f68..7876929 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
@@ -19,7 +19,6 @@
 import static com.android.cts.devicepolicy.DeviceAndProfileOwnerTest.DEVICE_ADMIN_APK;
 import static com.android.cts.devicepolicy.DeviceAndProfileOwnerTest.DEVICE_ADMIN_PKG;
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -51,7 +50,7 @@
 
     @Test
     public void testAdbDeviceOwnerLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -66,7 +65,7 @@
 
     @Test
     public void testAdbProfileOwnerLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
index 345c91e..bd3d402 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
@@ -5,7 +5,6 @@
 import static android.stats.devicepolicy.EventId.START_ACTIVITY_BY_INTENT_VALUE;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -223,7 +222,7 @@
     @LargeTest
     @Test
     public void testStartMainActivity_logged() throws Exception {
-        if (!mHasManagedUserFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasManagedUserFeature) {
             return;
         }
         assertMetricsLogged(
@@ -244,7 +243,7 @@
     @LargeTest
     @Test
     public void testGetTargetUserProfiles_logged() throws Exception {
-        if (!mHasManagedUserFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasManagedUserFeature) {
             return;
         }
         assertMetricsLogged(
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 4746fc4..a61756d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -228,7 +227,7 @@
 
     @Test
     public void testInstallCaCertLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -296,15 +295,13 @@
             // The DPC should still be able to manage app restrictions normally.
             executeDeviceTestClass(".ApplicationRestrictionsTest");
 
-            if (isStatsdEnabled(getDevice())) {
-                assertMetricsLogged(getDevice(), () -> {
-                    executeDeviceTestMethod(".ApplicationRestrictionsTest",
-                            "testSetApplicationRestrictions");
-                }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_RESTRICTIONS_VALUE)
-                        .setAdminPackageName(DEVICE_ADMIN_PKG)
-                        .setStrings(APP_RESTRICTIONS_TARGET_APP_PKG)
-                        .build());
-            }
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".ApplicationRestrictionsTest",
+                        "testSetApplicationRestrictions");
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_RESTRICTIONS_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(APP_RESTRICTIONS_TARGET_APP_PKG)
+                    .build());
         } finally {
             changeApplicationRestrictionsManagingPackage(null);
         }
@@ -439,7 +436,10 @@
             return;
         }
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionGrantStateDenied_permissionRemainsDenied");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionGrantStateGranted_permissionRemainsGranted");
     }
 
     @Test
@@ -575,7 +575,7 @@
     @RequiresDevice
     @Test
     public void testAlwaysOnVpnPackageLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         // Will be uninstalled in tearDown().
@@ -596,7 +596,10 @@
             return;
         }
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionPolicy");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionPolicyAutoDeny_permissionLocked");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionPolicyAutoGrant_permissionLocked");
     }
 
     @Test
@@ -605,7 +608,8 @@
             return;
         }
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testAutoGrantMultiplePermissionsInGroup");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionPolicyAutoGrant_multiplePermissionsInGroup");
     }
 
     @Test
@@ -614,7 +618,10 @@
             return;
         }
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionMixedPolicies");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionGrantStateDenied_mixedPolicies");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionGrantStateGranted_mixedPolicies");
     }
 
     @Test
@@ -625,7 +632,7 @@
         }
         installAppPermissionAppAsUser();
         executeDeviceTestMethod(".PermissionsTest",
-                "testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted");
+                "testPermissionGrantStateDenied_otherPermissionIsGranted");
     }
 
     // Test flakey; suppressed.
@@ -644,31 +651,27 @@
             return;
         }
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_setDeniedState");
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkDenied");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStateDenied");
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkDenied");
+        executeDeviceTestMethod(".PermissionsTest", "testCannotRequestPermission");
 
         assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_setGrantedState");
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkGranted");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStateGranted");
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkGranted");
+        executeDeviceTestMethod(".PermissionsTest", "testCanRequestPermission");
 
         assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_setAutoDeniedPolicy");
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkDenied");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionPolicyAutoDeny");
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkDenied");
+        executeDeviceTestMethod(".PermissionsTest", "testCannotRequestPermission");
 
         assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_setAutoGrantedPolicy");
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkGranted");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionPolicyAutoGrant");
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkGranted");
+        executeDeviceTestMethod(".PermissionsTest", "testCanRequestPermission");
     }
 
     @Test
@@ -677,7 +680,7 @@
             return;
         }
         installAppAsUser(SIMPLE_PRE_M_APP_APK, mUserId);
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStatePreMApp");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState_preMApp");
     }
 
     @Test
@@ -686,16 +689,14 @@
             return;
         }
         executeDeviceTestClass(".PersistentIntentResolvingTest");
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".PersistentIntentResolvingTest",
-                        "testAddPersistentPreferredActivityYieldsReceptionAtTarget");
-            }, new DevicePolicyEventWrapper.Builder(EventId.ADD_PERSISTENT_PREFERRED_ACTIVITY_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings(DEVICE_ADMIN_PKG,
-                            "com.android.cts.deviceandprofileowner.EXAMPLE_ACTION")
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".PersistentIntentResolvingTest",
+                    "testAddPersistentPreferredActivityYieldsReceptionAtTarget");
+        }, new DevicePolicyEventWrapper.Builder(EventId.ADD_PERSISTENT_PREFERRED_ACTIVITY_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings(DEVICE_ADMIN_PKG,
+                        "com.android.cts.deviceandprofileowner.EXAMPLE_ACTION")
+                .build());
     }
 
     @Test
@@ -744,20 +745,16 @@
             return;
         }
         executeDeviceTestClass(".SupportMessageTest");
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(
-                        ".SupportMessageTest", "testShortSupportMessageSetGetAndClear");
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_SHORT_SUPPORT_MESSAGE_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .build());
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".SupportMessageTest",
-                        "testLongSupportMessageSetGetAndClear");
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_LONG_SUPPORT_MESSAGE_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".SupportMessageTest", "testShortSupportMessageSetGetAndClear");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_SHORT_SUPPORT_MESSAGE_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .build());
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".SupportMessageTest", "testLongSupportMessageSetGetAndClear");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_LONG_SUPPORT_MESSAGE_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .build());
     }
 
     @Test
@@ -767,22 +764,19 @@
         }
         installAppPermissionAppAsUser();
         executeDeviceTestClass(".ApplicationHiddenTest");
-        if (isStatsdEnabled(getDevice())) {
-            installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".ApplicationHiddenTest",
-                        "testSetApplicationHidden");
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setBoolean(false)
-                    .setStrings(PERMISSIONS_APP_PKG, "hidden", NOT_CALLED_FROM_PARENT)
-                    .build(),
-            new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setBoolean(false)
-                    .setStrings(PERMISSIONS_APP_PKG, "not_hidden", NOT_CALLED_FROM_PARENT)
-                    .build());
-        }
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".ApplicationHiddenTest","testSetApplicationHidden");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setStrings(PERMISSIONS_APP_PKG, "hidden", NOT_CALLED_FROM_PARENT)
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setStrings(PERMISSIONS_APP_PKG, "not_hidden", NOT_CALLED_FROM_PARENT)
+                .build());
     }
 
     @Test
@@ -875,15 +869,13 @@
 
 
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest", mUserId);
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest",
-                        "testInstallKeyPair", mUserId);
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_CERT_INSTALLER_PACKAGE_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings(CERT_INSTALLER_PKG)
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest",
+                    "testInstallKeyPair", mUserId);
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_CERT_INSTALLER_PACKAGE_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings(CERT_INSTALLER_PKG)
+                .build());
     }
 
     public interface DelegatedCertInstallerTestAction {
@@ -1142,7 +1134,7 @@
 
     @Test
     public void testDisallowAdjustVolumeMutedLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1167,17 +1159,14 @@
         try {
             installAppAsUser(INTENT_RECEIVER_APK, mUserId);
             executeDeviceTestClass(".LockTaskTest");
-            if (isStatsdEnabled(getDevice())) {
-                assertMetricsLogged(
-                        getDevice(),
-                        () -> executeDeviceTestMethod(".LockTaskTest", "testStartLockTask"),
-                        new DevicePolicyEventWrapper.Builder(
-                                EventId.SET_LOCKTASK_MODE_ENABLED_VALUE)
-                                .setAdminPackageName(DEVICE_ADMIN_PKG)
-                                .setBoolean(true)
-                                .setStrings(DEVICE_ADMIN_PKG)
-                                .build());
-            }
+            assertMetricsLogged(
+                    getDevice(),
+                    () -> executeDeviceTestMethod(".LockTaskTest", "testStartLockTask"),
+                    new DevicePolicyEventWrapper.Builder(EventId.SET_LOCKTASK_MODE_ENABLED_VALUE)
+                            .setAdminPackageName(DEVICE_ADMIN_PKG)
+                            .setBoolean(true)
+                            .setStrings(DEVICE_ADMIN_PKG)
+                            .build());
         } catch (AssertionError ex) {
             // STOPSHIP(b/32771855), remove this once we fixed the bug.
             executeShellCommand("dumpsys activity activities");
@@ -1402,7 +1391,7 @@
 
     @Test
     public void testSetCameraDisabledLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1605,7 +1594,7 @@
 
     @Test
     public void testInstallKeyPairLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
 
@@ -1623,7 +1612,7 @@
 
     @Test
     public void testGenerateKeyPairLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
 
@@ -1647,7 +1636,7 @@
 
     @Test
     public void testSetKeyPairCertificateLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
 
@@ -1666,26 +1655,24 @@
         }
 
         executeDeviceTestClass(".AccessibilityServicesTest");
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".AccessibilityServicesTest",
-                        "testPermittedAccessibilityServices");
-            }, new DevicePolicyEventWrapper
-                    .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings((String[]) null)
-                    .build(),
-            new DevicePolicyEventWrapper
-                    .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings((String[]) null)
-                    .build(),
-            new DevicePolicyEventWrapper
-                    .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings("com.google.pkg.one", "com.google.pkg.two")
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".AccessibilityServicesTest",
+                    "testPermittedAccessibilityServices");
+        }, new DevicePolicyEventWrapper
+                .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings((String[]) null)
+                .build(),
+        new DevicePolicyEventWrapper
+                .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings((String[]) null)
+                .build(),
+        new DevicePolicyEventWrapper
+                .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings("com.google.pkg.one", "com.google.pkg.two")
+                .build());
     }
 
     @Test
@@ -1695,22 +1682,20 @@
         }
 
         executeDeviceTestClass(".InputMethodsTest");
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".InputMethodsTest", "testPermittedInputMethods");
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings((String[]) null)
-                    .build(),
-            new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings((String[]) null)
-                    .build(),
-            new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings("com.google.pkg.one", "com.google.pkg.two")
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".InputMethodsTest", "testPermittedInputMethods");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings((String[]) null)
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings((String[]) null)
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings("com.google.pkg.one", "com.google.pkg.two")
+                .build());
     }
 
     @Test
@@ -1726,7 +1711,7 @@
 
     @Test
     public void testPasswordMethodsLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
 
@@ -1769,7 +1754,7 @@
 
     @Test
     public void testLockNowLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1782,7 +1767,7 @@
 
     @Test
     public void testSetKeyguardDisabledFeaturesLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1812,7 +1797,7 @@
 
     @Test
     public void testSetKeyguardDisabledSecureCameraLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1836,7 +1821,7 @@
 
     @Test
     public void testSetUserRestrictionLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1871,7 +1856,7 @@
 
     @Test
     public void testSetSecureSettingLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1898,7 +1883,7 @@
 
     @Test
     public void testSetPermissionPolicyLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1923,7 +1908,7 @@
 
     @Test
     public void testSetPermissionGrantStateLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         installAppPermissionAppAsUser();
@@ -2003,7 +1988,7 @@
 
     @Test
     public void testEnableSystemAppLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         final List<String> enabledSystemPackageNames = getEnabledSystemPackageNames();
@@ -2023,7 +2008,7 @@
 
     @Test
     public void testEnableSystemAppWithIntentLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         final String systemPackageToEnable = getLaunchableSystemPackage();
@@ -2044,7 +2029,7 @@
 
     @Test
     public void testSetUninstallBlockedLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         installAppAsUser(PERMISSIONS_APP_APK, mUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
index 7a43fb3..dfeaa05 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
@@ -58,7 +58,7 @@
             return;
         }
         installAppAsUser(SIMPLE_PRE_M_APP_APK, mUserId);
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStateAppPreMDeviceAdminPreQ");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState_preMApp_preQDeviceAdmin");
     }
 
     @Test
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
index d54dea3..f535cd4 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -217,7 +216,7 @@
 
     @Test
     public void testWipeData_secondaryUserLogged() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1) || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
         }
         int secondaryUserId = setupManagedSecondaryUser();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index d9fc9eb..bf6dc4a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -518,7 +517,7 @@
     @Test
     @Ignore("b/145932189")
     public void testSetSystemUpdatePolicyLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -651,22 +650,19 @@
             return;
         }
         executeDeviceOwnerTest("AdminActionBookkeepingTest");
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRetrieveSecurityLogs");
-            }, new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_SECURITY_LOGS_VALUE)
-                    .setAdminPackageName(DEVICE_OWNER_PKG)
-                    .build(),
-            new DevicePolicyEventWrapper.Builder(
-                    EventId.RETRIEVE_PRE_REBOOT_SECURITY_LOGS_VALUE)
-                    .setAdminPackageName(DEVICE_OWNER_PKG)
-                    .build());
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRequestBugreport");
-            }, new DevicePolicyEventWrapper.Builder(EventId.REQUEST_BUGREPORT_VALUE)
-                    .setAdminPackageName(DEVICE_OWNER_PKG)
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRetrieveSecurityLogs");
+        }, new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_SECURITY_LOGS_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_PRE_REBOOT_SECURITY_LOGS_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .build());
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRequestBugreport");
+        }, new DevicePolicyEventWrapper.Builder(EventId.REQUEST_BUGREPORT_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .build());
     }
 
     @Test
@@ -884,7 +880,7 @@
 
     @Test
     public void testSetKeyguardDisabledLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -896,7 +892,7 @@
 
     @Test
     public void testSetStatusBarDisabledLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
index 5492cf2..eae6f3a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
@@ -90,35 +89,32 @@
                 contactsTestSet.checkIfCanFilterEnterpriseContacts(false);
                 contactsTestSet.checkIfCanFilterSelfContacts();
                 contactsTestSet.checkIfNoEnterpriseDirectoryFound();
-                if (isStatsdEnabled(getDevice())) {
-                    assertMetricsLogged(getDevice(), () -> {
-                        contactsTestSet.setCallerIdEnabled(true);
-                        contactsTestSet.setCallerIdEnabled(false);
-                    }, new DevicePolicyEventWrapper
-                            .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
-                            .setAdminPackageName(MANAGED_PROFILE_PKG)
-                            .setBoolean(false)
-                            .build(),
-                    new DevicePolicyEventWrapper
-                            .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
-                            .setAdminPackageName(MANAGED_PROFILE_PKG)
-                            .setBoolean(true)
-                            .build());
-                    assertMetricsLogged(getDevice(), () -> {
-                        contactsTestSet.setContactsSearchEnabled(true);
-                        contactsTestSet.setContactsSearchEnabled(false);
-                    }, new DevicePolicyEventWrapper
-                            .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
-                            .setAdminPackageName(MANAGED_PROFILE_PKG)
-                            .setBoolean(false)
-                            .build(),
-                    new DevicePolicyEventWrapper
-                            .Builder(
-                            EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
-                            .setAdminPackageName(MANAGED_PROFILE_PKG)
-                            .setBoolean(true)
-                            .build());
-                }
+                assertMetricsLogged(getDevice(), () -> {
+                    contactsTestSet.setCallerIdEnabled(true);
+                    contactsTestSet.setCallerIdEnabled(false);
+                }, new DevicePolicyEventWrapper
+                        .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
+                        .setAdminPackageName(MANAGED_PROFILE_PKG)
+                        .setBoolean(false)
+                        .build(),
+                new DevicePolicyEventWrapper
+                        .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
+                        .setAdminPackageName(MANAGED_PROFILE_PKG)
+                        .setBoolean(true)
+                        .build());
+                assertMetricsLogged(getDevice(), () -> {
+                    contactsTestSet.setContactsSearchEnabled(true);
+                    contactsTestSet.setContactsSearchEnabled(false);
+                }, new DevicePolicyEventWrapper
+                        .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
+                        .setAdminPackageName(MANAGED_PROFILE_PKG)
+                        .setBoolean(false)
+                        .build(),
+                new DevicePolicyEventWrapper
+                        .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
+                        .setAdminPackageName(MANAGED_PROFILE_PKG)
+                        .setBoolean(true)
+                        .build());
                 return null;
             } finally {
                 // reset policies
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
index 9873fb2..78fa910 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
@@ -24,7 +24,6 @@
 import static android.stats.devicepolicy.EventId.SET_INTERACT_ACROSS_PROFILES_APP_OP_VALUE;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -87,17 +86,15 @@
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
                 MANAGED_PROFILE_PKG + ".CrossProfileIntentFilterTest", mProfileUserId);
 
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                runDeviceTestsAsUser(
-                        MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".CrossProfileIntentFilterTest",
-                        "testAddCrossProfileIntentFilter_all", mProfileUserId);
-            }, new DevicePolicyEventWrapper.Builder(ADD_CROSS_PROFILE_INTENT_FILTER_VALUE)
-                    .setAdminPackageName(MANAGED_PROFILE_PKG)
-                    .setInt(1)
-                    .setStrings("com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY")
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(
+                    MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".CrossProfileIntentFilterTest",
+                    "testAddCrossProfileIntentFilter_all", mProfileUserId);
+        }, new DevicePolicyEventWrapper.Builder(ADD_CROSS_PROFILE_INTENT_FILTER_VALUE)
+                .setAdminPackageName(MANAGED_PROFILE_PKG)
+                .setInt(1)
+                .setStrings("com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY")
+                .build());
 
         // Set up filters from primary to managed profile
         String command = "am start -W --user " + mProfileUserId + " " + MANAGED_PROFILE_PKG
@@ -297,7 +294,7 @@
     @FlakyTest
     @Test
     public void testCrossProfileWidgetsLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
 
@@ -621,7 +618,7 @@
             throws Exception {
         Set<String> currentPids = new HashSet<>(
                 Arrays.asList(getAppPid(packageName).split(" ")));
-        assertThat(currentPids).containsAllIn(pids);
+        assertThat(currentPids).containsAtLeastElementsIn(pids);
     }
 
     private void assertAppKilledInBothProfiles(String packageName,  List<String> pids)
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
index bf32483..7df857c 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
@@ -223,7 +222,7 @@
 
     @Test
     public void testCreateSeparateChallengeChangedLogged() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature || !mHasSecureLockScreen) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index fb4c5e9..107161f 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -16,7 +16,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -331,15 +330,13 @@
                 "testDefaultOrganizationNameIsNull", mProfileUserId);
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".OrganizationInfoTest",
                 mProfileUserId);
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                runDeviceTestsAsUser(
-                        MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".OrganizationInfoTest",
-                        "testSetOrganizationColor", mProfileUserId);
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_ORGANIZATION_COLOR_VALUE)
-                    .setAdminPackageName(MANAGED_PROFILE_PKG)
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(
+                    MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".OrganizationInfoTest",
+                    "testSetOrganizationColor", mProfileUserId);
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_ORGANIZATION_COLOR_VALUE)
+                .setAdminPackageName(MANAGED_PROFILE_PKG)
+                .build());
     }
 
     @Test
@@ -606,7 +603,7 @@
 
     @Test
     public void testSetProfileNameLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java
index 60367d0..76e85c7 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertTrue;
 
@@ -79,7 +78,7 @@
     @FlakyTest
     @Test
     public void testWipeDataLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertTrue(listUsers().contains(mProfileUserId));
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 0c23013..cd7bd53 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -153,13 +152,11 @@
             return;
         }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".WifiTest", "testGetWifiMacAddress", mUserId);
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".WifiTest", "testGetWifiMacAddress");
-            }, new DevicePolicyEventWrapper.Builder(EventId.GET_WIFI_MAC_ADDRESS_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".WifiTest", "testGetWifiMacAddress");
+        }, new DevicePolicyEventWrapper.Builder(EventId.GET_WIFI_MAC_ADDRESS_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .build());
     }
 
     @Test
@@ -235,13 +232,11 @@
 
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".LockScreenInfoTest", mUserId);
 
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".LockScreenInfoTest", "testSetAndGetLockInfo");
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_DEVICE_OWNER_LOCK_SCREEN_INFO_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".LockScreenInfoTest", "testSetAndGetLockInfo");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_DEVICE_OWNER_LOCK_SCREEN_INFO_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .build());
     }
 
     @Test
@@ -303,7 +298,7 @@
 
     @Test
     public void testInstallUpdateLogged() throws Exception {
-        if (!mHasFeature || !isDeviceAb() || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature || !isDeviceAb()) {
             return;
         }
         pushUpdateFileToDevice("wrongHash.zip");
@@ -367,7 +362,7 @@
 
     @Test
     public void testSecurityLoggingEnabledLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -411,7 +406,8 @@
         }
         installAppPermissionAppAsUser();
         configureNotificationListener();
-        executeDeviceTestMethod(".PermissionsTest", "testUserNotifiedOfLocationPermissionGrant");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionGrantStateGranted_userNotifiedOfLocationPermission");
     }
 
     private void configureNotificationListener() throws DeviceNotAvailableException {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 739b1d5..1447f82 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -299,7 +299,6 @@
     }
 
     @Override
-    @FlakyTest
     @PermissionsTest
     @Test
     public void testPermissionGrant() throws Exception {
@@ -307,7 +306,6 @@
     }
 
     @Override
-    @FlakyTest
     @PermissionsTest
     @Test
     public void testPermissionMixedPolicies() throws Exception {
@@ -323,7 +321,6 @@
 
     @Override
     @PermissionsTest
-    @FlakyTest(bugId = 145350538)
     @Test
     public void testPermissionPolicy() throws Exception {
         super.testPermissionPolicy();
@@ -338,7 +335,6 @@
 
     @Override
     @PermissionsTest
-    @FlakyTest(bugId = 145350538)
     @Test
     public void testPermissionAppUpdate() throws Exception {
         super.testPermissionAppUpdate();
@@ -346,7 +342,6 @@
 
     @Override
     @PermissionsTest
-    @FlakyTest(bugId = 145350538)
     @Test
     public void testPermissionGrantPreMApp() throws Exception {
         super.testPermissionGrantPreMApp();
@@ -354,7 +349,6 @@
 
     @Override
     @PermissionsTest
-    @FlakyTest(bugId = 145350538)
     @Test
     public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted()
             throws Exception {
@@ -418,4 +412,11 @@
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdentifiersTest",
                 "testProfileOwnerCanGetDeviceIdentifiersWithPermission", mUserId);
     }
+
+    @Override
+    @LockSettingsTest
+    @Test
+    public void testSecondaryLockscreen() throws Exception {
+        // Managed profiles cannot have secondary lockscreens set.
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
index e69f2dc..f512c24 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -18,7 +18,6 @@
 
 import static com.android.cts.devicepolicy.DeviceAndProfileOwnerTest.DEVICE_ADMIN_COMPONENT_FLATTENED;
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -167,7 +166,7 @@
 
     @Test
     public void testUserRestrictionSetOnParentLogged() throws Exception {
-        if (!mHasFeature|| !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -243,7 +242,7 @@
 
     @Test
     public void testCameraDisabledOnParentLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -614,7 +613,7 @@
 
     @Test
     public void testSetPersonalAppsSuspendedLogged() throws Exception {
-        if (!mHasFeature|| !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -632,7 +631,7 @@
 
     @Test
     public void testSetManagedProfileMaximumTimeOffLogged() throws Exception {
-        if (!mHasFeature|| !isStatsdEnabled(getDevice())) {
+        if (!mHasFeature) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/AtomMetricTester.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/AtomMetricTester.java
index 3262632..5ab3ddb0 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/AtomMetricTester.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/AtomMetricTester.java
@@ -63,9 +63,6 @@
     }
 
     void cleanLogs() throws Exception {
-        if (isStatsdDisabled()) {
-            return;
-        }
         removeConfig(CONFIG_ID);
         getReportList(); // Clears data.
     }
@@ -236,14 +233,4 @@
         mDevice.executeShellCommand(command, receiver);
         return parser.parseFrom(receiver.getOutput());
     }
-
-    boolean isStatsdDisabled() throws DeviceNotAvailableException {
-        // if ro.statsd.enable doesn't exist, statsd is running by default.
-        if ("false".equals(mDevice.getProperty("ro.statsd.enable"))
-                && "true".equals(mDevice.getProperty("ro.config.low_ram"))) {
-            CLog.d("Statsd is not enabled on the device");
-            return true;
-        }
-        return false;
-    }
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
index c1cb1ee..f9f7a17 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
@@ -38,16 +38,11 @@
 
     /**
      * Asserts that <code>expectedLogs</code> were logged as a result of executing
-     * <code>action</code>, in the same order. Note that {@link Action#apply() } is always
-     * invoked on the <code>action</code> parameter, even if statsd logs are disabled.
+     * <code>action</code>, in the same order.
      */
     public static void assertMetricsLogged(ITestDevice device, Action action,
             DevicePolicyEventWrapper... expectedLogs) throws Exception {
         final AtomMetricTester logVerifier = new AtomMetricTester(device);
-        if (logVerifier.isStatsdDisabled()) {
-            action.apply();
-            return;
-        }
         try {
             logVerifier.cleanLogs();
             logVerifier.createAndUploadConfig(Atom.DEVICE_POLICY_EVENT_FIELD_NUMBER);
@@ -66,16 +61,11 @@
 
     /**
      * Asserts that <code>expectedLogs</code> were not logged as a result of executing
-     * <code>action</code>. Note that {@link Action#apply() } is always
-     * invoked on the <code>action</code> parameter, even if statsd expectedLogs are disabled.
+     * <code>action</code>.
      */
     public static void assertMetricsNotLogged(ITestDevice device, Action action,
             DevicePolicyEventWrapper... expectedLogs) throws Exception {
         final AtomMetricTester logVerifier = new AtomMetricTester(device);
-        if (logVerifier.isStatsdDisabled()) {
-            action.apply();
-            return;
-        }
         try {
             logVerifier.cleanLogs();
             logVerifier.createAndUploadConfig(Atom.DEVICE_POLICY_EVENT_FIELD_NUMBER);
@@ -92,11 +82,6 @@
         }
     }
 
-    public static boolean isStatsdEnabled(ITestDevice device) throws DeviceNotAvailableException {
-        final AtomMetricTester logVerifier = new AtomMetricTester(device);
-        return !logVerifier.isStatsdDisabled();
-    }
-
     private static void assertExpectedMetricLogged(List<EventMetricData> data,
             DevicePolicyEventWrapper expectedLog) {
         assertWithMessage("Expected metric was not logged.")
@@ -122,4 +107,4 @@
         });
         return closestMatches.contains(expectedLog);
     }
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/dexmetadata/app/SplitApp/AndroidManifest.xml b/hostsidetests/dexmetadata/app/SplitApp/AndroidManifest.xml
index 23ba9bc..cb79717 100644
--- a/hostsidetests/dexmetadata/app/SplitApp/AndroidManifest.xml
+++ b/hostsidetests/dexmetadata/app/SplitApp/AndroidManifest.xml
@@ -15,14 +15,15 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.dexmetadata.splitapp"
-        android:isolatedSplits="true">
+     package="com.android.cts.dexmetadata.splitapp"
+     android:isolatedSplits="true">
 
     <application android:debuggable="true">
-        <activity android:name=".BaseActivity">
+        <activity android:name=".BaseActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/AndroidManifest.xml b/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/AndroidManifest.xml
index 40b4c99..dedf124 100644
--- a/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/AndroidManifest.xml
+++ b/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/AndroidManifest.xml
@@ -15,15 +15,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.dexmetadata.splitapp"
-        android:isFeatureSplit="true"
-        split="feature_a">
+     package="com.android.cts.dexmetadata.splitapp"
+     android:isFeatureSplit="true"
+     split="feature_a">
 
     <application android:debuggable="true">
-        <activity android:name=".feature_a.FeatureAActivity">
+        <activity android:name=".feature_a.FeatureAActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml b/hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml
index 3a9f902..f5883d4 100644
--- a/hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml
+++ b/hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml
@@ -13,18 +13,20 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.framestatstestapp">
+     package="com.android.cts.framestatstestapp">
     <!--
-    A simple app that draws at least one frame. Used by framestats
-    test.
-    -->
+            A simple app that draws at least one frame. Used by framestats
+            test.
+            -->
     <application>
-        <activity android:name=".FramestatsTestAppActivity">
+        <activity android:name=".FramestatsTestAppActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index e30829c..5e6bb62 100755
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -758,7 +758,7 @@
     }
 
     private void validateProfileData(String profileData) throws IOException {
-        final int TIMESTAMP_COUNT = 14;
+        final int TIMESTAMP_COUNT = 15;
         boolean foundAtLeastOneRow = false;
         try (BufferedReader reader = new BufferedReader(
                 new StringReader(profileData))) {
@@ -768,10 +768,10 @@
 
             assertNotNull(line);
             assertTrue("First line was not the expected header",
-                    line.startsWith("Flags,IntendedVsync,Vsync,OldestInputEvent" +
-                            ",NewestInputEvent,HandleInputStart,AnimationStart" +
-                            ",PerformTraversalsStart,DrawStart,SyncQueued,SyncStart" +
-                            ",IssueDrawCommandsStart,SwapBuffers,FrameCompleted"));
+                    line.startsWith("Flags,FrameTimelineVsyncId,IntendedVsync,Vsync" +
+                            ",OldestInputEvent,NewestInputEvent,HandleInputStart" +
+                            ",AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued" +
+                            ",SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted"));
 
             long[] numparts = new long[TIMESTAMP_COUNT];
             while ((line = reader.readLine()) != null && !line.isEmpty()) {
@@ -786,16 +786,16 @@
                     continue;
                 }
                 // assert VSYNC >= INTENDED_VSYNC
-                assertTrue(numparts[2] >= numparts[1]);
-                // assert time is flowing forwards, skipping index 3 & 4
+                assertTrue(numparts[3] >= numparts[2]);
+                // assert time is flowing forwards, skipping index 4 & 5
                 // as those are input timestamps that may or may not be present
-                assertTrue(numparts[5] >= numparts[2]);
-                for (int i = 6; i < TIMESTAMP_COUNT; i++) {
+                assertTrue(numparts[6] >= numparts[3]);
+                for (int i = 7; i < TIMESTAMP_COUNT; i++) {
                     assertTrue("Index " + i + " did not flow forward, " +
                             numparts[i] + " not larger than " + numparts[i - 1],
                             numparts[i] >= numparts[i-1]);
                 }
-                long totalDuration = numparts[13] - numparts[1];
+                long totalDuration = numparts[14] - numparts[2];
                 assertTrue("Frame did not take a positive amount of time to process",
                         totalDuration > 0);
                 assertTrue("Bogus frame duration, exceeds 100 seconds",
diff --git a/hostsidetests/gputools/apps/AndroidManifest.xml b/hostsidetests/gputools/apps/AndroidManifest.xml
index a4fd8dc..89ecaf8 100755
--- a/hostsidetests/gputools/apps/AndroidManifest.xml
+++ b/hostsidetests/gputools/apps/AndroidManifest.xml
@@ -16,17 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.rootlessgpudebug.app">
+     package="android.rootlessgpudebug.app">
 
-    <application android:extractNativeLibs="true" >
-        <activity android:name=".RootlessGpuDebugDeviceActivity" >
+    <application android:extractNativeLibs="true">
+        <activity android:name=".RootlessGpuDebugDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
-
diff --git a/hostsidetests/gputools/apps/inject/AndroidManifest.xml b/hostsidetests/gputools/apps/inject/AndroidManifest.xml
index e16aedb..63bd9c1 100644
--- a/hostsidetests/gputools/apps/inject/AndroidManifest.xml
+++ b/hostsidetests/gputools/apps/inject/AndroidManifest.xml
@@ -16,18 +16,18 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.rootlessgpudebug.app">
+     package="android.rootlessgpudebug.app">
 
-    <application android:extractNativeLibs="true" >
-        <meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
-        <activity android:name=".RootlessGpuDebugDeviceActivity" >
+    <application android:extractNativeLibs="true">
+        <meta-data android:name="com.android.graphics.injectLayers.enable"
+             android:value="true"/>
+        <activity android:name=".RootlessGpuDebugDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
-
diff --git a/hostsidetests/graphics/gpuprofiling/TEST_MAPPING b/hostsidetests/graphics/gpuprofiling/TEST_MAPPING
new file mode 100644
index 0000000..ca81f4a
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsGpuProfilingDataTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/harmfulappwarning/TEST_MAPPING b/hostsidetests/harmfulappwarning/TEST_MAPPING
new file mode 100644
index 0000000..80c945b
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsHarmfulAppWarningHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/harmfulappwarning/sampleapp/AndroidManifest.xml b/hostsidetests/harmfulappwarning/sampleapp/AndroidManifest.xml
index 5bfbd24..08a3c12 100755
--- a/hostsidetests/harmfulappwarning/sampleapp/AndroidManifest.xml
+++ b/hostsidetests/harmfulappwarning/sampleapp/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.harmfulappwarning.sampleapp">
+     package="android.harmfulappwarning.sampleapp">
 
     <application>
-        <activity android:name=".SampleDeviceActivity" >
+        <activity android:name=".SampleDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/hdmicec/app/AndroidManifest.xml b/hostsidetests/hdmicec/app/AndroidManifest.xml
index e21fb53..90403e5 100644
--- a/hostsidetests/hdmicec/app/AndroidManifest.xml
+++ b/hostsidetests/hdmicec/app/AndroidManifest.xml
@@ -16,25 +16,27 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.hdmicec.app">
+     package="android.hdmicec.app">
     <uses-feature android:name="android.software.leanback"
-        android:required="false" />
+         android:required="false"/>
     <uses-permission android:name="android.permission.HDMI_CEC" />
-    <application >
-        <activity android:name=".HdmiCecKeyEventCapture" >
+    <application>
+        <activity android:name=".HdmiCecKeyEventCapture"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
             </intent-filter>
         </activity>
-         <activity android:name=".HdmiCecAudioManager" >
+         <activity android:name=".HdmiCecAudioManager"
+              android:exported="true">
             <intent-filter>
-                <action android:name="android.hdmicec.app.MUTE" />
-                <action android:name="android.hdmicec.app.UNMUTE" />
-                <action android:name="android.hdmicec.app.REPORT_VOLUME" />
-                <action android:name="android.hdmicec.app.SET_VOLUME" />
-                <action android:name="android.hdmicec.app.GET_SUPPORTED_SAD_FORMATS" />
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+                <action android:name="android.hdmicec.app.MUTE"/>
+                <action android:name="android.hdmicec.app.UNMUTE"/>
+                <action android:name="android.hdmicec.app.REPORT_VOLUME"/>
+                <action android:name="android.hdmicec.app.SET_VOLUME"/>
+                <action android:name="android.hdmicec.app.GET_SUPPORTED_SAD_FORMATS"/>
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".HdmiControlManagerHelper" >
diff --git a/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml b/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml
index 9825d5c..e2b1268 100644
--- a/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml
+++ b/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml
@@ -16,34 +16,36 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-  package="android.appwidget.cts">
+     package="android.appwidget.cts">
 
     <application>
 
       <uses-library android:name="android.test.runner"/>
 
-      <receiver android:name="android.appwidget.cts.provider.FirstAppWidgetProvider" >
+      <receiver android:name="android.appwidget.cts.provider.FirstAppWidgetProvider"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/appwidget_info" />
+               android:resource="@xml/appwidget_info"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.SecondAppWidgetProvider" >
+      <receiver android:name="android.appwidget.cts.provider.SecondAppWidgetProvider"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/appwidget_info" />
+               android:resource="@xml/appwidget_info"/>
       </receiver>
 
   </application>
 
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-      android:targetPackage="android.appwidget.cts"
-      android:label="CTS Tests for the dumpsys protobuf protocol">
+       android:targetPackage="android.appwidget.cts"
+       android:label="CTS Tests for the dumpsys protobuf protocol">
       <meta-data android:name="listener"
-          android:value="com.android.cts.runner.CtsTestRunListener" />
+           android:value="com.android.cts.runner.CtsTestRunListener"/>
   </instrumentation>
 </manifest>
diff --git a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
index 8093dc3..a5cb9d7 100644
--- a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
@@ -16,18 +16,18 @@
 
 package com.android.server.cts;
 
-import com.android.server.AlarmClockMetadataProto;
-import com.android.server.AlarmManagerServiceDumpProto;
-import com.android.server.AlarmProto;
-import com.android.server.BatchProto;
-import com.android.server.BroadcastStatsProto;
-import com.android.server.ConstantsProto;
-import com.android.server.FilterStatsProto;
+import com.android.server.alarm.AlarmClockMetadataProto;
+import com.android.server.alarm.AlarmManagerServiceDumpProto;
+import com.android.server.alarm.AlarmProto;
+import com.android.server.alarm.BatchProto;
+import com.android.server.alarm.BroadcastStatsProto;
+import com.android.server.alarm.ConstantsProto;
+import com.android.server.alarm.FilterStatsProto;
 import com.android.server.AppStateTrackerProto;
 import com.android.server.AppStateTrackerProto.RunAnyInBackgroundRestrictedPackages;
-import com.android.server.IdleDispatchEntryProto;
-import com.android.server.InFlightProto;
-import com.android.server.WakeupEventProto;
+import com.android.server.alarm.IdleDispatchEntryProto;
+import com.android.server.alarm.InFlightProto;
+import com.android.server.alarm.WakeupEventProto;
 import java.util.List;
 
 /**
@@ -64,10 +64,10 @@
             // 0 is technically a valid UID.
             assertTrue(0 <= uid);
         }
-        for (int aid : appStateTracker.getPowerSaveWhitelistAppIdsList()) {
+        for (int aid : appStateTracker.getPowerSaveExemptAppIdsList()) {
             assertTrue(0 <= aid);
         }
-        for (int aid : appStateTracker.getTempPowerSaveWhitelistAppIdsList()) {
+        for (int aid : appStateTracker.getTempPowerSaveExemptAppIdsList()) {
             assertTrue(0 <= aid);
         }
         for (RunAnyInBackgroundRestrictedPackages r : appStateTracker.getRunAnyInBackgroundRestrictedPackagesList()) {
@@ -87,7 +87,7 @@
         assertTrue(0 < dump.getTimeSinceLastWakeupSetMs());
         assertTrue(0 <= dump.getTimeChangeEventCount());
 
-        for (int aid : dump.getDeviceIdleUserWhitelistAppIdsList()) {
+        for (int aid : dump.getDeviceIdleUserExemptAppIdsList()) {
             assertTrue(0 <= aid);
         }
 
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
index a262209..ba3a907 100644
--- a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
@@ -24,9 +24,6 @@
     private static final String TAG = "IncidentdTest";
 
     public void testIncidentReportDump(final int filterLevel, final String dest) throws Exception {
-        if (incidentdDisabled()) {
-            return;
-        }
         final String destArg = dest == null || dest.isEmpty() ? "" : "-p " + dest;
         final IncidentProto dump = getDump(IncidentProto.parser(), "incident " + destArg + " 2>/dev/null");
 
diff --git a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
index 13718a9..4d7f684 100644
--- a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
+++ b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
@@ -266,14 +266,4 @@
         }
         return false;
     }
-
-    protected boolean incidentdDisabled() throws DeviceNotAvailableException {
-        // if ro.statsd.enable doesn't exist, incidentd is disabled as well.
-        if ("false".equals(getDevice().getProperty("ro.statsd.enable"))
-                && "true".equals(getDevice().getProperty("ro.config.low_ram"))) {
-            CLog.d("Incidentd is not enabled on the device.");
-            return true;
-        }
-        return false;
-    }
 }
diff --git a/hostsidetests/incrementalinstall/app/Android.bp b/hostsidetests/incrementalinstall/app/Android.bp
index 85a8be1..c269477 100644
--- a/hostsidetests/incrementalinstall/app/Android.bp
+++ b/hostsidetests/incrementalinstall/app/Android.bp
@@ -39,6 +39,33 @@
     use_embedded_native_libs: false,
 }
 
+// v1 implementation of test app built with v1 manifest with uncompressed native libs.
+android_test_helper_app {
+    name: "IncrementalTestAppUncompressed",
+    srcs: ["v1/src/**/*.java"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    test_suites: [
+        "cts",
+    ],
+    v4_signature: true,
+    static_libs: [
+        "incremental-install-common-lib",
+    ],
+    sdk_version: "test_current",
+    export_package_resources: true,
+    aapt_include_all_resources: true,
+    manifest: "AndroidManifestV1.xml",
+
+    // This flag allow the native lib to be uncompressed in the apk or associated split apk, and
+    // does not need to be extracted by the installer.
+    use_embedded_native_libs: true,
+}
+
 // v2 implementation of test app built with v1 manifest for zero version update test.
 android_test_helper_app {
     name: "IncrementalTestApp2_v1",
diff --git a/hostsidetests/incrementalinstall/app/AndroidManifestV1.xml b/hostsidetests/incrementalinstall/app/AndroidManifestV1.xml
index 68d6d3d..881fbea 100644
--- a/hostsidetests/incrementalinstall/app/AndroidManifestV1.xml
+++ b/hostsidetests/incrementalinstall/app/AndroidManifestV1.xml
@@ -16,12 +16,13 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.incrementalinstall.incrementaltestapp"
-          android:versionCode="1"
-          android:versionName="1.0">
+     package="android.incrementalinstall.incrementaltestapp"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <application android:label="IncrementalTestApp">
-        <activity android:name=".MainActivity">
+        <activity android:name=".MainActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/hostsidetests/incrementalinstall/app/AndroidManifestV2.xml b/hostsidetests/incrementalinstall/app/AndroidManifestV2.xml
index 27cfbb9..e027fdd 100644
--- a/hostsidetests/incrementalinstall/app/AndroidManifestV2.xml
+++ b/hostsidetests/incrementalinstall/app/AndroidManifestV2.xml
@@ -16,12 +16,13 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.incrementalinstall.incrementaltestapp"
-          android:versionCode="2"
-          android:versionName="2.0">
+     package="android.incrementalinstall.incrementaltestapp"
+     android:versionCode="2"
+     android:versionName="2.0">
 
     <application android:label="IncrementalTestApp">
-        <activity android:name=".MainActivity">
+        <activity android:name=".MainActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/hostsidetests/incrementalinstall/app/nativelibuncompressed/Android.bp b/hostsidetests/incrementalinstall/app/nativelibuncompressed/Android.bp
index 3421fb8..910c45f 100644
--- a/hostsidetests/incrementalinstall/app/nativelibuncompressed/Android.bp
+++ b/hostsidetests/incrementalinstall/app/nativelibuncompressed/Android.bp
@@ -32,7 +32,7 @@
     compile_multilib: "both",
     export_package_resources: true,
         aapt_include_all_resources: true,
-        libs: ["IncrementalTestApp"],
+        libs: ["IncrementalTestAppUncompressed"],
         aaptflags: [
             "--rename-manifest-package android.incrementalinstall.incrementaltestapp",
             "--package-id 0x83",
diff --git a/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java b/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java
index 6e7adc6..fc90987 100644
--- a/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java
+++ b/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java
@@ -84,6 +84,8 @@
     private static final String TEST_APP_DYNAMIC_CODE_NAME = "IncrementalTestAppDynamicCode.apk";
     private static final String TEST_APP_COMPRESSED_NATIVE_NAME =
             "IncrementalTestAppCompressedNativeLib.apk";
+    private static final String TEST_APP_UNCOMPRESSED_BASE_NAME =
+            "IncrementalTestAppUncompressed.apk";
     private static final String TEST_APP_UNCOMPRESSED_NATIVE_NAME =
             "IncrementalTestAppUncompressedNativeLib.apk";
 
@@ -205,7 +207,7 @@
         assertTrue(checkNativeLibInApkCompression(TEST_APP_COMPRESSED_NATIVE_NAME,
                 "libuncompressednativeincrementaltest.so", false));
         verifyInstallCommandSuccess(
-                installWithAdbInstaller(TEST_APP_BASE_APK_NAME, TEST_APP_UNCOMPRESSED_NATIVE_NAME));
+                installWithAdbInstaller(TEST_APP_UNCOMPRESSED_BASE_NAME, TEST_APP_UNCOMPRESSED_NATIVE_NAME));
         verifyPackageInstalled(TEST_APP_PACKAGE_NAME);
         verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
                 TEST_APP_V1_VERSION);
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
index 5314c9c..19c1440 100755
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
@@ -16,34 +16,34 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.inputmethodservice.cts.devicetest" android:targetSandboxVersion="2">
+     package="android.inputmethodservice.cts.devicetest"
+     android:targetSandboxVersion="2">
 
     <!--
-      TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
-      latest OS behaviors.
-    -->
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+              TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
+              latest OS behaviors.
+            -->
+    <uses-sdk android:minSdkVersion="28"
+         android:targetSdkVersion="28"/>
 
-    <application
-        android:label="CtsInputMethodServiceDeviceTests"
-        android:icon="@mipmap/ic_launcher"
-        android:allowBackup="false">
+    <application android:label="CtsInputMethodServiceDeviceTests"
+         android:icon="@mipmap/ic_launcher"
+         android:allowBackup="false">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name=".TestActivity"
-            android:label="TestActivity">
+        <activity android:name=".TestActivity"
+             android:label="TestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.inputmethodservice.cts.devicetest" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.inputmethodservice.cts.devicetest"/>
 
 </manifest>
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
index 70de83f..b0eaaa7 100755
--- a/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
@@ -16,29 +16,27 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.inputmethodservice.cts.ime1">
+     package="android.inputmethodservice.cts.ime1">
 
     <!--
-      TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
-      latest OS behaviors.
-    -->
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
+              TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
+              latest OS behaviors.
+            -->
+    <uses-sdk android:minSdkVersion="19"
+         android:targetSdkVersion="25"/>
 
-    <application
-        android:label="@string/ime_name"
-        android:allowBackup="false"
-        android:theme="@android:style/Theme.InputMethod"
-    >
-        <service
-            android:name=".CtsInputMethod1"
-            android:label="@string/ime_name"
-            android:permission="android.permission.BIND_INPUT_METHOD">
+    <application android:label="@string/ime_name"
+         android:allowBackup="false"
+         android:theme="@android:style/Theme.InputMethod">
+        <service android:name=".CtsInputMethod1"
+             android:label="@string/ime_name"
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
-            <meta-data
-                android:name="android.view.im"
-                android:resource="@xml/ime1" />
+            <meta-data android:name="android.view.im"
+                 android:resource="@xml/ime1"/>
         </service>
     </application>
 
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
index a166ba3..0168b8d 100755
--- a/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
@@ -16,29 +16,27 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.inputmethodservice.cts.ime2">
+     package="android.inputmethodservice.cts.ime2">
 
     <!--
-      TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
-      latest OS behaviors.
-    -->
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
+              TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
+              latest OS behaviors.
+            -->
+    <uses-sdk android:minSdkVersion="19"
+         android:targetSdkVersion="25"/>
 
-    <application
-        android:label="@string/ime_name"
-        android:allowBackup="false"
-        android:theme="@android:style/Theme.InputMethod"
-    >
-        <service
-            android:name=".CtsInputMethod2"
-            android:label="@string/ime_name"
-            android:permission="android.permission.BIND_INPUT_METHOD">
+    <application android:label="@string/ime_name"
+         android:allowBackup="false"
+         android:theme="@android:style/Theme.InputMethod">
+        <service android:name=".CtsInputMethod2"
+             android:label="@string/ime_name"
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
-            <meta-data
-                android:name="android.view.im"
-                android:resource="@xml/ime2" />
+            <meta-data android:name="android.view.im"
+                 android:resource="@xml/ime2"/>
         </service>
     </application>
 
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
index 841b7c1..a8b17e3 100755
--- a/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
@@ -16,18 +16,19 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.inputmethodservice.cts.provider">
+     package="android.inputmethodservice.cts.provider">
 
-    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26" />
+    <uses-sdk android:minSdkVersion="26"
+         android:targetSdkVersion="26"/>
 
     <application android:label="CtsInputMethodServiceEventProvider">
-        <provider
-            android:authorities="android.inputmethodservice.cts.provider"
-            android:name="android.inputmethodservice.cts.provider.EventProvider"
-            android:exported="true" />
-        <receiver android:name="android.inputmethodservice.cts.receiver.EventReceiver">
+        <provider android:authorities="android.inputmethodservice.cts.provider"
+             android:name="android.inputmethodservice.cts.provider.EventProvider"
+             android:exported="true"/>
+        <receiver android:name="android.inputmethodservice.cts.receiver.EventReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.inputmethodservice.cts.action.IME_EVENT" />
+                <action android:name="android.inputmethodservice.cts.action.IME_EVENT"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/install/Android.bp b/hostsidetests/install/Android.bp
new file mode 100644
index 0000000..0f1d005
--- /dev/null
+++ b/hostsidetests/install/Android.bp
@@ -0,0 +1,54 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "CtsInstallHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs:  ["src/**/*.java"],
+    libs: [
+        "cts-tradefed",
+        "cts-shim-host-lib",
+        "tradefed",
+        "truth-prebuilt",
+        "hamcrest",
+        "hamcrest-library",
+    ],
+    data: [
+        ":InstallTest",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "InstallTest",
+    srcs:  ["app/src/**/*.java", "src/android/cts/install/*.java"],
+    manifest : "app/AndroidManifest.xml",
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.core",
+        "truth-prebuilt",
+        "cts-install-lib",
+        "cts-rollback-lib",
+    ],
+    java_resources: [
+        ":StagedInstallTestApexV1",
+        ":StagedInstallTestApexV2",
+        ":StagedInstallTestApexV3",
+    ],
+    sdk_version: "test_current",
+    test_suites: ["device-tests"],
+}
diff --git a/hostsidetests/install/AndroidTest.xml b/hostsidetests/install/AndroidTest.xml
new file mode 100644
index 0000000..35073c7
--- /dev/null
+++ b/hostsidetests/install/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs the install API tests">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can't have INSTALL_PACKAGES permission. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <!-- TODO(b/137885984): Revisit secondary user eligibility once the issue is resolved. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="InstallTest.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="android.cts.install.host.InstallTest" />
+        <option name="class" value="android.cts.install.host.DowngradeTest" />
+        <option name="class" value="android.cts.install.host.SamegradeTest" />
+        <option name="class" value="android.cts.install.host.UpgradeTest" />
+    </test>
+</configuration>
diff --git a/hostsidetests/install/OWNERS b/hostsidetests/install/OWNERS
new file mode 100644
index 0000000..71cfd5a
--- /dev/null
+++ b/hostsidetests/install/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36137
+include ../Stagedinstall/OWNERS
+chenghsiuchang@google.com
\ No newline at end of file
diff --git a/hostsidetests/install/TEST_MAPPING b/hostsidetests/install/TEST_MAPPING
new file mode 100644
index 0000000..8ba921a
--- /dev/null
+++ b/hostsidetests/install/TEST_MAPPING
@@ -0,0 +1,17 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsInstallHostTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.LargeTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsInstallHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/install/app/AndroidManifest.xml b/hostsidetests/install/app/AndroidManifest.xml
new file mode 100644
index 0000000..01130f2
--- /dev/null
+++ b/hostsidetests/install/app/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.cts.install">
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
+             android:exported="true"/>
+        <!-- This activity is necessary to register the test app as the default home activity (i.e.
+                         to receive SESSION_COMMITTED broadcasts.) -->
+        <activity android:name=".LauncherActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.HOME"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.cts.install"
+         android:label="Install Test"/>
+</manifest>
diff --git a/hostsidetests/install/app/src/android/cts/install/DowngradeTest.java b/hostsidetests/install/app/src/android/cts/install/DowngradeTest.java
new file mode 100644
index 0000000..13e6f94
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/DowngradeTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class DowngradeTest {
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mStaged;
+
+    @Parameter(2)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Staged{1}_Rollback{2}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean staged : booleanValues) {
+                for (boolean enableRollback : booleanValues) {
+                    temp.add(new Object[]{installType, staged, enableRollback});
+                }
+            }
+        }
+        return temp;
+    }
+
+    @Rule
+    public InstallRule mInstallRule = new InstallRule();
+
+    @Rule
+    public SessionRule mSessionRule = new SessionRule();
+
+    private static final int VERSION_CODE_CURRENT = 3;
+    private static final int VERSION_CODE_DOWNGRADE = 2;
+
+    /**
+     * Cleans up test environment.
+     *
+     * This is marked as @Test to take advantage of @Before/@After methods of hostside test cases.
+     * Actual purpose of this method to be called before and after each test case of
+     * {@link android.cts.install.host.DowngradeTest} to reduce tests flakiness.
+     */
+    @Test
+    public void cleanUp_phase() throws Exception {
+        mInstallRule.cleanUp();
+        mSessionRule.cleanUp();
+    }
+
+    /** Install the version {@link #VERSION_CODE_CURRENT} of the apps to be downgraded. */
+    @Test
+    public void arrange_phase() throws Exception {
+        getParameterizedInstall(VERSION_CODE_CURRENT).commit();
+    }
+
+    /** Verify the version of the arranged apps. */
+    @Test
+    public void assert_postArrange_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    /** Commits the downgrade. */
+    @Test
+    public void action_phase() throws Exception {
+        Install install = getParameterizedInstall(VERSION_CODE_DOWNGRADE);
+        int sessionId = install.setRequestDowngrade().commit();
+        mSessionRule.recordSessionId(sessionId);
+    }
+
+    /** Confirms target version of the apps installed. */
+    @Test
+    public void assert_downgradeSuccess_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_DOWNGRADE);
+    }
+
+    /** Confirms versions before staged downgrades applied. */
+    @Test
+    public void assert_preReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionReady()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    /** Confirms versions after staged downgrades applied. */
+    @Test
+    public void assert_postReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionApplied()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_DOWNGRADE);
+    }
+
+    /** Confirms the downgrade commit not allowed. */
+    @Test
+    public void assert_downgradeNotAllowed_phase() {
+        Install install = getParameterizedInstall(VERSION_CODE_DOWNGRADE).setRequestDowngrade();
+        InstallUtils.commitExpectingFailure(AssertionError.class,
+                "INSTALL_FAILED_VERSION_DOWNGRADE" + "|"
+                        + "Downgrade of APEX package com\\.android\\.apex\\.cts\\.shim is not "
+                        + "allowed",
+                install);
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    /** Confirms the downgrade commit not allowed without requesting downgrade. */
+    @Test
+    public void assert_downgradeNotRequested_phase() {
+        Install install = getParameterizedInstall(VERSION_CODE_DOWNGRADE);
+        InstallUtils.commitExpectingFailure(AssertionError.class,
+                "INSTALL_FAILED_VERSION_DOWNGRADE" + "|"
+                        + "Downgrade of APEX package com\\.android\\.apex\\.cts\\.shim is not "
+                        + "allowed",
+                install);
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    /** Gets parameterized {@link Install} of test packages with specific version. */
+    private Install getParameterizedInstall(int versionCode) {
+        List<TestApp> testApps = mInstallRule.getTestApps(mInstallType, versionCode);
+        Install install = testApps.size() == 1
+                ? Install.single(testApps.get(0))
+                : Install.multi(testApps.toArray(new TestApp[testApps.size()]));
+        if (mStaged) {
+            install.setStaged();
+        }
+        if (mEnableRollback) {
+            install.setEnableRollback();
+        }
+        return install;
+    }
+}
diff --git a/hostsidetests/install/app/src/android/cts/install/InstallRule.java b/hostsidetests/install/app/src/android/cts/install/InstallRule.java
new file mode 100644
index 0000000..a7d701b
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/InstallRule.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install;
+
+import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
+import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.Manifest;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+
+import org.junit.rules.ExternalResource;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Adopts needed permissions for testing install flow and provides test packages mappings
+ * corresponding to {@link INSTALL_TYPE}.
+ */
+final class InstallRule extends ExternalResource {
+    private static final String TAG = InstallRule.class.getSimpleName();
+
+    static final int VERSION_CODE_INVALID = -1;
+    static final int VERSION_CODE_DEFAULT = 1;
+
+    /** Indexes test apps with package name and versionCode */
+    private static final Table<String, Integer, TestApp> sTestAppMap = getTestAppTable();
+
+    @Override
+    protected void before() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        Manifest.permission.INSTALL_PACKAGES,
+                        Manifest.permission.DELETE_PACKAGES);
+    }
+
+    @Override
+    protected void after() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    /**
+     * Performs cleanup phase for test installations. Actual purpose of this method is to be called
+     * before and after each host-side test to reduce tests flakiness.
+     */
+    void cleanUp() throws Exception {
+        PackageInstaller packageInstaller = getPackageInstaller();
+        packageInstaller.getStagedSessions().stream()
+                .filter(sessionInfo -> !sessionInfo.hasParentSessionId())
+                .forEach(sessionInfo -> {
+                    try {
+                        Log.i(TAG, "abandoning session " + sessionInfo.getSessionId());
+                        packageInstaller.abandonSession(sessionInfo.getSessionId());
+                    } catch (Exception e) {
+                        Log.e(TAG, "Failed to abandon session " + sessionInfo.getSessionId(), e);
+                    }
+                });
+        Uninstall.packages(TestApp.A, TestApp.B);
+    }
+
+    /** Resolves corresponding test apps with specific install type and version. */
+    List<TestApp> getTestApps(INSTALL_TYPE installType, int versionCode) {
+        return getTestPackageNames(installType).stream()
+                .map(packageName -> getTestApp(packageName, versionCode))
+                .collect(Collectors.toList());
+    }
+
+    /** Asserts {@code packageNames} has been installed with expected version. */
+    void assertPackageVersion(INSTALL_TYPE installType, int version) {
+        getTestPackageNames(installType).stream().forEach(packageName -> {
+            long installedVersion = VERSION_CODE_INVALID;
+            long expectedVersion = version;
+
+            PackageInfo info = InstallUtils.getPackageInfo(packageName);
+            if (info != null) {
+                installedVersion = info.getLongVersionCode();
+                if (version == VERSION_CODE_INVALID && info.isApex) {
+                    // Apex cannot be fully uninstalled, verify default version instead.
+                    expectedVersion = VERSION_CODE_DEFAULT;
+                }
+            }
+
+            assertWithMessage(
+                    String.format("%s's versionCode expected to be %d, but was %d",
+                            packageName, expectedVersion, installedVersion))
+                    .that(installedVersion).isEqualTo(expectedVersion);
+        });
+    }
+
+    /**
+     * Resolves corresponding test packages.
+     *
+     * @note This method should be aligned with {@link INSTALL_TYPE}
+     */
+    private static List<String> getTestPackageNames(INSTALL_TYPE installType) {
+        switch (installType) {
+            case SINGLE_APK:
+                return Arrays.asList(TestApp.A);
+            case SINGLE_APEX:
+                return Arrays.asList(SHIM_APEX_PACKAGE_NAME);
+            case MULTIPLE_APKS:
+                return Arrays.asList(TestApp.A, TestApp.B);
+            case MULTIPLE_MIX:
+                return Arrays.asList(TestApp.A, SHIM_APEX_PACKAGE_NAME);
+            default:
+                throw new AssertionError("Unknown install type");
+        }
+    }
+
+    private static TestApp getTestApp(String packageName, int version) {
+        if (!sTestAppMap.contains(packageName, version)) {
+            throw new AssertionError("Unknown test app");
+        }
+        return sTestAppMap.get(packageName, version);
+    }
+
+    private static Table<String, Integer, TestApp> getTestAppTable() {
+        Table<String, Integer, TestApp> testAppMap = HashBasedTable.create();
+        testAppMap.put(TestApp.A, 1, TestApp.A1);
+        testAppMap.put(TestApp.A, 2, TestApp.A2);
+        testAppMap.put(TestApp.A, 3, TestApp.A3);
+        testAppMap.put(TestApp.B, 1, TestApp.B1);
+        testAppMap.put(TestApp.B, 2, TestApp.B2);
+        testAppMap.put(TestApp.B, 3, TestApp.B3);
+        testAppMap.put(SHIM_APEX_PACKAGE_NAME, 1, TestApp.Apex1);
+        testAppMap.put(SHIM_APEX_PACKAGE_NAME, 2, TestApp.Apex2);
+        testAppMap.put(SHIM_APEX_PACKAGE_NAME, 3, TestApp.Apex3);
+        return testAppMap;
+    }
+}
diff --git a/hostsidetests/install/app/src/android/cts/install/InstallTest.java b/hostsidetests/install/app/src/android/cts/install/InstallTest.java
new file mode 100644
index 0000000..5a5dd54
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/InstallTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install;
+
+import static android.cts.install.InstallRule.VERSION_CODE_INVALID;
+
+import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInstaller;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(Parameterized.class)
+public final class InstallTest {
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mStaged;
+
+    @Parameter(2)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Staged{1}_Rollback{2}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean staged : booleanValues) {
+                for (boolean enableRollback : booleanValues) {
+                    temp.add(new Object[]{installType, staged, enableRollback});
+                }
+            }
+        }
+        return temp;
+    }
+
+    @Rule
+    public InstallRule mInstallRule = new InstallRule();
+
+    @Rule
+    public SessionRule mSessionRule = new SessionRule();
+
+    private static final int VERSION_CODE_TARGET = 2;
+
+    /**
+     * Cleans up test environment.
+     *
+     * This is marked as @Test to take advantage of @Before/@After methods of hostside test cases.
+     * Actual purpose of this method to be called before and after each test case of
+     * {@link android.cts.install.host.InstallTest} to reduce tests flakiness.
+     */
+    @Test
+    public void cleanUp_phase() throws Exception {
+        mInstallRule.cleanUp();
+        mSessionRule.cleanUp();
+    }
+
+    @Test
+    public void action_phase() throws Exception {
+        Install install = getParameterizedInstall(VERSION_CODE_TARGET);
+        int sessionId = install.commit();
+        mSessionRule.recordSessionId(sessionId);
+    }
+
+    @Test
+    public void assert_commitFailure_phase() {
+        Install install = getParameterizedInstall(VERSION_CODE_TARGET);
+        InstallUtils.commitExpectingFailure(IllegalArgumentException.class,
+                "APEX files can only be installed as part of a staged session.", install);
+    }
+
+    @Test
+    public void assert_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_TARGET);
+    }
+
+    @Test
+    public void assert_preReboot_phase() throws Exception {
+        assertNoSessionCommitBroadcastSent();
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionReady()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_INVALID);
+    }
+
+    @Test
+    public void assert_postReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionApplied()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_TARGET);
+        assertNoSessionCommitBroadcastSent();
+    }
+
+    @Test
+    public void action_abandonSession_phase() throws Exception {
+        getPackageInstaller().abandonSession(mSessionRule.retrieveSessionId());
+    }
+
+    @Test
+    public void assert_abandonSession_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_INVALID);
+    }
+
+    /** Gets parameterized {@link Install} of test packages with specific version. */
+    private Install getParameterizedInstall(int versionCode) {
+        List<TestApp> testApps = mInstallRule.getTestApps(mInstallType, versionCode);
+        Install install = testApps.size() == 1
+                ? Install.single(testApps.get(0))
+                : Install.multi(testApps.toArray(new TestApp[testApps.size()]));
+        if (mStaged) {
+            install.setStaged();
+        }
+        if (mEnableRollback) {
+            install.setEnableRollback();
+        }
+        return install;
+    }
+
+    private static void assertNoSessionCommitBroadcastSent() throws InterruptedException {
+        BlockingQueue<PackageInstaller.SessionInfo> committedSessions = new LinkedBlockingQueue<>();
+        BroadcastReceiver sessionCommittedReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                try {
+                    PackageInstaller.SessionInfo info =
+                            intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
+                    committedSessions.put(info);
+                } catch (InterruptedException e) {
+                    throw new AssertionError(e);
+                }
+            }
+        };
+
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        context.registerReceiver(sessionCommittedReceiver,
+                new IntentFilter(PackageInstaller.ACTION_SESSION_COMMITTED));
+
+        PackageInstaller.SessionInfo info = committedSessions.poll(10, TimeUnit.SECONDS);
+        context.unregisterReceiver(sessionCommittedReceiver);
+
+        assertThat(info).isNull();
+    }
+}
diff --git a/hostsidetests/install/app/src/android/cts/install/LauncherActivity.java b/hostsidetests/install/app/src/android/cts/install/LauncherActivity.java
new file mode 100644
index 0000000..d91b0ae
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/LauncherActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install;
+
+import android.app.Activity;
+
+public class LauncherActivity extends Activity {
+}
\ No newline at end of file
diff --git a/hostsidetests/install/app/src/android/cts/install/SamegradeTest.java b/hostsidetests/install/app/src/android/cts/install/SamegradeTest.java
new file mode 100644
index 0000000..2b82fef
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/SamegradeTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install;
+
+import static com.android.cts.install.lib.InstallUtils.getPackageInfo;
+import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class SamegradeTest {
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mStaged;
+
+    @Parameter(2)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Staged{1}_Rollback{2}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean staged : booleanValues) {
+                for (boolean enableRollback : booleanValues) {
+                    temp.add(new Object[]{installType, staged, enableRollback});
+                }
+            }
+        }
+        return temp;
+    }
+
+    @Rule
+    public InstallRule mInstallRule = new InstallRule();
+
+    @Rule
+    public SessionRule mSessionRule = new SessionRule();
+
+    private static final int VERSION_CODE_SAMEGRADE = 2;
+    private static final int VERSION_CODE_SAMEGRADE_SYSTEM = 1;
+
+    /**
+     * Cleans up test environment.
+     *
+     * This is marked as @Test to take advantage of @Before/@After methods of hostside test cases.
+     * Actual purpose of this method to be called before and after each test case to reduce tests
+     * flakiness.
+     */
+    @Test
+    public void cleanUp_phase() throws Exception {
+        mInstallRule.cleanUp();
+        mSessionRule.cleanUp();
+    }
+
+    /** Install the version {@link #VERSION_CODE_SAMEGRADE} of the apps to be samegraded. */
+    @Test
+    public void arrange_phase() throws Exception {
+        getParameterizedInstall(VERSION_CODE_SAMEGRADE).commit();
+    }
+
+    @Test
+    public void assert_postArrange_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_SAMEGRADE);
+    }
+
+    @Test
+    public void action_phase() throws Exception {
+        Install install = getParameterizedInstall(VERSION_CODE_SAMEGRADE);
+        int sessionId = install.commit();
+        mSessionRule.recordSessionId(sessionId);
+    }
+
+    @Test
+    public void assert_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_SAMEGRADE);
+    }
+
+    /** Confirms versions before staged samegrades applied. */
+    @Test
+    public void assert_preReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionReady()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_SAMEGRADE);
+    }
+
+    /** Confirms versions after staged samegrades applied. */
+    @Test
+    public void assert_postReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionApplied()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_SAMEGRADE);
+    }
+
+    @Test
+    public void action_systemApex_phase() throws Exception {
+        final PackageInfo shim = getPackageInfo(SHIM_APEX_PACKAGE_NAME);
+        assertThat(shim).isNotNull();
+        assertThat(shim.getLongVersionCode())
+                .isEqualTo(VERSION_CODE_SAMEGRADE_SYSTEM);
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                .isEqualTo(ApplicationInfo.FLAG_SYSTEM);
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
+                .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
+
+        int sessionId = getParameterizedInstall(VERSION_CODE_SAMEGRADE_SYSTEM).commit();
+        mSessionRule.recordSessionId(sessionId);
+    }
+
+    @Test
+    public void assert_systemApex_postReboot_phase() throws Exception {
+        final int INSTALLED_ON_DATA_PART = 0;
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionApplied()).isTrue();
+
+        final PackageInfo shim = getPackageInfo(SHIM_APEX_PACKAGE_NAME);
+        assertThat(shim).isNotNull();
+        assertThat(shim.getLongVersionCode())
+                .isEqualTo(VERSION_CODE_SAMEGRADE_SYSTEM);
+
+        // Check that APEX on /data wins.
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                .isEqualTo(INSTALLED_ON_DATA_PART);
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
+                .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
+        assertThat(shim.applicationInfo.sourceDir)
+                .isEqualTo("/data/apex/active/com.android.apex.cts.shim@1.apex");
+        assertThat(shim.applicationInfo.publicSourceDir)
+                .isEqualTo(shim.applicationInfo.sourceDir);
+    }
+
+    /** Gets parameterized {@link Install} of test packages with specific version. */
+    private Install getParameterizedInstall(int versionCode) {
+        List<TestApp> testApps = mInstallRule.getTestApps(mInstallType, versionCode);
+        Install install = testApps.size() == 1
+                ? Install.single(testApps.get(0))
+                : Install.multi(testApps.toArray(new TestApp[testApps.size()]));
+        if (mStaged) {
+            install.setStaged();
+        }
+        if (mEnableRollback) {
+            install.setEnableRollback();
+        }
+        return install;
+    }
+}
diff --git a/hostsidetests/install/app/src/android/cts/install/SessionRule.java b/hostsidetests/install/app/src/android/cts/install/SessionRule.java
new file mode 100644
index 0000000..5c95c4f
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/SessionRule.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install;
+
+import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
+
+import android.content.Context;
+import android.content.pm.PackageInstaller;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.rules.ExternalResource;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Optional;
+
+/** Utils for recording session state and retrieving recorded session info. */
+final class SessionRule extends ExternalResource {
+    private static final String STATE_FILENAME = "ctsstagedinstall_state";
+
+    private final Context mContext;
+    private final File mTestStateFile;
+
+    SessionRule() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mTestStateFile = new File(mContext.getFilesDir(), STATE_FILENAME);
+    }
+
+    @Override
+    protected void before() throws Throwable {
+        mTestStateFile.createNewFile();
+    }
+
+    /**
+     * Performs cleanup phase for this rule. Actual purpose of this method is to be called before
+     * and after each host-side test to reduce tests flakiness.
+     */
+    void cleanUp() throws IOException {
+        Files.deleteIfExists(mTestStateFile.toPath());
+    }
+
+    void recordSessionId(int sessionId) throws IOException {
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(mTestStateFile))) {
+            writer.write(String.valueOf(sessionId));
+        }
+    }
+
+    int retrieveSessionId() throws IOException {
+        try (BufferedReader reader = new BufferedReader(new FileReader(mTestStateFile))) {
+            return Integer.parseInt(reader.readLine());
+        }
+    }
+
+    /**
+     * Returns {@link android.content.pm.PackageInstaller.SessionInfo} with session id recorded
+     * in {@link #mTestStateFile}. Assert error if no session found.
+     */
+    PackageInstaller.SessionInfo retrieveSessionInfo() throws IOException {
+        return Optional.of(getPackageInstaller().getSessionInfo(retrieveSessionId()))
+                .orElseThrow(() -> new AssertionError(
+                        "Expecting to find session with getSessionInfo()"));
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/install/app/src/android/cts/install/UpgradeTest.java b/hostsidetests/install/app/src/android/cts/install/UpgradeTest.java
new file mode 100644
index 0000000..81559e9
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/UpgradeTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class UpgradeTest {
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mStaged;
+
+    @Parameter(2)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Staged{1}_Rollback{2}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean staged : booleanValues) {
+                for (boolean enableRollback : booleanValues) {
+                    temp.add(new Object[]{installType, staged, enableRollback});
+                }
+            }
+        }
+        return temp;
+    }
+
+    @Rule
+    public InstallRule mInstallRule = new InstallRule();
+
+    @Rule
+    public SessionRule mSessionRule = new SessionRule();
+
+    private static final int VERSION_CODE_CURRENT = 2;
+    private static final int VERSION_CODE_UPGRADE = 3;
+
+    @Test
+    public void cleanUp_phase() throws Exception {
+        mInstallRule.cleanUp();
+        mSessionRule.cleanUp();
+    }
+
+    /** Install the version {@link #VERSION_CODE_CURRENT} of the apps to be upgraded. */
+    @Test
+    public void arrange_phase() throws Exception {
+        getParameterizedInstall(VERSION_CODE_CURRENT).commit();
+    }
+
+    @Test
+    public void assert_postArrange_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    @Test
+    public void action_phase() throws Exception {
+        Install install = getParameterizedInstall(VERSION_CODE_UPGRADE);
+        int sessionId = install.commit();
+        mSessionRule.recordSessionId(sessionId);
+    }
+
+    @Test
+    public void assert_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_UPGRADE);
+    }
+
+    /** Confirms versions before staged samegrades applied. */
+    @Test
+    public void assert_preReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionReady()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    /** Confirms versions after staged samegrades applied. */
+    @Test
+    public void assert_postReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionApplied()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_UPGRADE);
+    }
+
+    /** Gets parameterized {@link Install} of test packages with specific version. */
+    private Install getParameterizedInstall(int versionCode) {
+        List<TestApp> testApps = mInstallRule.getTestApps(mInstallType, versionCode);
+        Install install = testApps.size() == 1
+                ? Install.single(testApps.get(0))
+                : Install.multi(testApps.toArray(new TestApp[testApps.size()]));
+        if (mStaged) {
+            install.setStaged();
+        }
+        if (mEnableRollback) {
+            install.setEnableRollback();
+        }
+        return install;
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/INSTALL_TYPE.java b/hostsidetests/install/src/android/cts/install/INSTALL_TYPE.java
new file mode 100644
index 0000000..ef98e07
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/INSTALL_TYPE.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install;
+
+/**
+ * Indicates target test packages of the installation test.
+ *
+ * @note Once the enum changed, remember to update
+ * {@link InstallRule#getTestPackageNames(INSTALL_TYPE)} correspondingly as well.
+ */
+// TODO(b/136152558): Supports MULTIPLE_APEXS type
+public enum INSTALL_TYPE {
+    SINGLE_APK,
+    SINGLE_APEX,
+    MULTIPLE_APKS,
+    MULTIPLE_MIX;
+
+    /** Returns true if the install type are testing apex package. */
+    public boolean containsApex() {
+        switch (this) {
+            case SINGLE_APEX:
+            case MULTIPLE_MIX:
+                return true;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/DeviceParameterized.java b/hostsidetests/install/src/android/cts/install/host/DeviceParameterized.java
new file mode 100644
index 0000000..9e7743a
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/DeviceParameterized.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install.host;
+
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.ITestInformationReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runners.Parameterized;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters;
+import org.junit.runners.parameterized.ParametersRunnerFactory;
+import org.junit.runners.parameterized.TestWithParameters;
+
+import java.util.List;
+
+/**
+ * Custom JUnit4 parameterized test runner that also accommodate {@link ITestInformationReceiver}
+ * to support {@link BaseHostJUnit4Test#getDevice()} properly.
+ */
+public final class DeviceParameterized extends Parameterized implements ITestInformationReceiver {
+    private TestInformation mTestInformation;
+    private List<Runner> mRunners;
+
+    public DeviceParameterized(Class<?> klass) throws Throwable {
+        super(klass);
+        mRunners = super.getChildren();
+    }
+
+    @Override
+    public void setTestInformation(TestInformation testInformation) {
+        mTestInformation = testInformation;
+        for (Runner runner: mRunners) {
+            if (runner instanceof  ITestInformationReceiver) {
+                ((ITestInformationReceiver)runner).setTestInformation(mTestInformation);
+            }
+        }
+    }
+
+    @Override
+    public TestInformation getTestInformation() {
+        return mTestInformation;
+    }
+
+    public static class RunnerFactory implements ParametersRunnerFactory {
+        @Override
+        public Runner createRunnerForTestWithParameters(TestWithParameters test)
+                throws InitializationError {
+            return new DeviceParameterizedRunner(test);
+        }
+    }
+
+    public static class DeviceParameterizedRunner
+            extends BlockJUnit4ClassRunnerWithParameters implements ITestInformationReceiver {
+        private TestInformation mTestInformation;
+
+        public DeviceParameterizedRunner(TestWithParameters test) throws InitializationError {
+            super(test);
+        }
+
+        /** Overrides createTest in order to set the device. */
+        @Override
+        public Object createTest() throws Exception {
+            Object testObj = super.createTest();
+            if (testObj instanceof ITestInformationReceiver) {
+                if (mTestInformation == null) {
+                    throw new IllegalArgumentException("Missing test info");
+                }
+                ((ITestInformationReceiver) testObj).setTestInformation(mTestInformation);
+            }
+            return testObj;
+        }
+
+        @Override
+        public void setTestInformation(TestInformation testInformation) {
+            mTestInformation = testInformation;
+        }
+
+        @Override
+        public TestInformation getTestInformation() {
+            return mTestInformation;
+        }
+
+        @Override
+        public Description getDescription() {
+            // Make sure it includes test class name when generating parameterized test suites.
+            Description desc = Description.createSuiteDescription(getTestClass().getJavaClass());
+            // Invoke super getDescription to apply filtered children
+            super.getDescription().getChildren().forEach(child -> desc.addChild(child));
+            return desc;
+        }
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/DowngradeTest.java b/hostsidetests/install/src/android/cts/install/host/DowngradeTest.java
new file mode 100644
index 0000000..fe6afac
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/DowngradeTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.INSTALL_TYPE;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(DeviceParameterized.class)
+@UseParametersRunnerFactory(DeviceParameterized.RunnerFactory.class)
+public final class DowngradeTest extends BaseHostJUnit4Test {
+    private static final String PACKAGE_NAME = "android.cts.install";
+    private static final String PHASE_FORMAT_SUFFIX = "[%s_Staged%b_Rollback%b]";
+
+    private static final String CLEAN_UP_PHASE = "cleanUp_phase";
+    private static final String ARRANGE_PHASE = "arrange_phase";
+    private static final String ASSERT_POST_ARRANGE_PHASE = "assert_postArrange_phase";
+    private static final String ACTION_PHASE = "action_phase";
+    private static final String ASSERT_DOWNGRADE_SUCCESS_PHASE = "assert_downgradeSuccess_phase";
+    private static final String ASSERT_POST_REBOOT_PHASE = "assert_postReboot_phase";
+    private static final String ASSERT_PRE_REBOOT_PHASE = "assert_preReboot_phase";
+    private static final String ASSERT_DOWNGRADE_NOT_ALLOWED_PHASE =
+            "assert_downgradeNotAllowed_phase";
+    private static final String ASSERT_DOWNGRADE_NOT_REQUESTED_PHASE =
+            "assert_downgradeNotRequested_phase";
+
+    @Rule
+    public ShimApexRule mShimApexRule = new ShimApexRule(this);
+
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Rollback{1}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean enableRollback : booleanValues) {
+                temp.add(new Object[]{installType, enableRollback});
+            }
+        }
+        return temp;
+    }
+
+    @Before
+    @After
+    public void cleanUp() throws Exception {
+        runPhase(CLEAN_UP_PHASE);
+    }
+
+    @Before
+    public void assumeApexSupported() throws DeviceNotAvailableException {
+        if (mInstallType.containsApex()) {
+            assumeTrue("Device does not support updating APEX",
+                    mShimApexRule.isUpdatingApexSupported());
+        }
+    }
+
+    @Test
+    public void testNonStagedDowngrade_downgradeNotRequested_fails() throws Exception {
+        // Apex should not be committed in non-staged install, such logic covered in InstallTest.
+        assumeFalse(mInstallType.containsApex());
+        runPhase(ARRANGE_PHASE);
+        runPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runPhase(ASSERT_DOWNGRADE_NOT_REQUESTED_PHASE);
+    }
+
+    @Test
+    public void testNonStagedDowngrade_debugBuild() throws Exception {
+        // Apex should not be committed in non-staged install, such logic covered in InstallTest.
+        assumeFalse(mInstallType.containsApex());
+        assumeTrue("Device is not debuggable", isDebuggable());
+        runPhase(ARRANGE_PHASE);
+        runPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runPhase(ACTION_PHASE);
+
+        runPhase(ASSERT_DOWNGRADE_SUCCESS_PHASE);
+    }
+
+    @Test
+    public void testNonStagedDowngrade_nonDebugBuild_fail() throws Exception {
+        // Apex should not be committed in non-staged install, such logic covered in InstallTest.
+        assumeFalse(mInstallType.containsApex());
+        assumeFalse("Device is debuggable", isDebuggable());
+        runPhase(ARRANGE_PHASE);
+        runPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runPhase(ASSERT_DOWNGRADE_NOT_ALLOWED_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedDowngrade_downgradeNotRequested_fails() throws Exception {
+        runStagedPhase(ARRANGE_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runStagedPhase(ASSERT_DOWNGRADE_NOT_REQUESTED_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedDowngrade_debugBuild() throws Exception {
+        assumeTrue("Device is not debuggable", isDebuggable());
+        runStagedPhase(ARRANGE_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runStagedPhase(ACTION_PHASE);
+
+        runStagedPhase(ASSERT_PRE_REBOOT_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_REBOOT_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedDowngrade_nonDebugBuild_fail() throws Exception {
+        assumeFalse("Device is debuggable", isDebuggable());
+        runStagedPhase(ARRANGE_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runStagedPhase(ASSERT_DOWNGRADE_NOT_ALLOWED_PHASE);
+    }
+
+    private void runPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, false /* staged */);
+    }
+
+    private void runStagedPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, true /* staged */);
+    }
+
+    /**
+     * Runs the given phase of a test with parameters by calling into the device.
+     * Throws an exception if the test phase fails.
+     * <p>
+     * For example, <code>runPhase("action_phase", true);</code>
+     */
+    private void runPhase(String phase, boolean staged) throws DeviceNotAvailableException {
+        assertThat(runDeviceTests(PACKAGE_NAME,
+                String.format("%s.%s", PACKAGE_NAME, this.getClass().getSimpleName()),
+                String.format(phase + PHASE_FORMAT_SUFFIX, mInstallType, staged, mEnableRollback)))
+                .isTrue();
+    }
+
+    private boolean isDebuggable() throws Exception {
+        return getDevice().getIntProperty("ro.debuggable", 0) == 1;
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/InstallTest.java b/hostsidetests/install/src/android/cts/install/host/InstallTest.java
new file mode 100644
index 0000000..5580466
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/InstallTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.INSTALL_TYPE;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(DeviceParameterized.class)
+@UseParametersRunnerFactory(DeviceParameterized.RunnerFactory.class)
+public final class InstallTest extends BaseHostJUnit4Test {
+    private static final String PACKAGE_NAME = "android.cts.install";
+    private static final String CUSTOMIZED_LAUNCHER_COMPONENT =
+            PACKAGE_NAME + "/" + PACKAGE_NAME + ".LauncherActivity";
+    private static final String PHASE_FORMAT_SUFFIX = "[%s_Staged%b_Rollback%b]";
+
+    private static final String CLEAN_UP_PHASE = "cleanUp_phase";
+    private static final String ACTION_PHASE = "action_phase";
+    private static final String ACTION_ABANDON_SESSION_PHASE = "action_abandonSession_phase";
+    private static final String ASSERT_PHASE = "assert_phase";
+    private static final String ASSERT_COMMIT_FAILURE_PHASE = "assert_commitFailure_phase";
+    private static final String ASSERT_PRE_REBOOT_PHASE = "assert_preReboot_phase";
+    private static final String ASSERT_POST_REBOOT_PHASE = "assert_postReboot_phase";
+    private static final String ASSERT_ABANDON_SESSION = "assert_abandonSession_phase";
+
+    @Rule
+    public ShimApexRule mShimApexRule = new ShimApexRule(this);
+
+    @Rule
+    public LauncherRule mLauncherRule = new LauncherRule(this, CUSTOMIZED_LAUNCHER_COMPONENT);
+
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Rollback{1}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType: INSTALL_TYPE.values()) {
+            for (boolean enableRollback: booleanValues) {
+                temp.add(new Object[]{installType, enableRollback});
+            }
+        }
+        return temp;
+    }
+
+    private boolean mStaged;
+
+    @Before
+    @After
+    public void cleanUp() throws Exception {
+        runPhase(CLEAN_UP_PHASE);
+    }
+
+    @Before
+    public void assumeApexSupported() throws DeviceNotAvailableException {
+        if (mInstallType.containsApex()) {
+            assumeTrue("Device does not support updating APEX",
+                    mShimApexRule.isUpdatingApexSupported());
+        }
+    }
+
+    @Test
+    public void testInstall() throws Exception {
+        mStaged = false;
+        if (mInstallType.containsApex()) {
+            runPhase(ASSERT_COMMIT_FAILURE_PHASE);
+            return;
+        }
+        runPhase(ACTION_PHASE);
+        runPhase(ASSERT_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedInstall() throws Exception {
+        mStaged = true;
+        runPhase(ACTION_PHASE);
+        runPhase(ASSERT_PRE_REBOOT_PHASE);
+        getDevice().reboot();
+        runPhase(ASSERT_POST_REBOOT_PHASE);
+    }
+
+    @Test
+    public void testAbandonStagedSessionBeforeReboot() throws Exception {
+        mStaged = true;
+        runPhase(ACTION_PHASE);
+        runPhase(ASSERT_PRE_REBOOT_PHASE);
+        runPhase(ACTION_ABANDON_SESSION_PHASE);
+        runPhase(ASSERT_ABANDON_SESSION);
+    }
+
+    @Test
+    @LargeTest
+    public void testAbandonStagedSessionAfterReboot() throws Exception {
+        mStaged = true;
+        runPhase(ACTION_PHASE);
+        getDevice().reboot();
+        runPhase(ACTION_ABANDON_SESSION_PHASE);
+        runPhase(ASSERT_POST_REBOOT_PHASE);
+    }
+
+    /**
+     * Runs the given phase of a test with parameters by calling into the device.
+     * Throws an exception if the test phase fails.
+     * <p>
+     * For example, <code>runPhase("action_phase");</code>
+     */
+    private void runPhase(String phase) throws DeviceNotAvailableException {
+        assertThat(runDeviceTests(PACKAGE_NAME,
+                String.format("%s.%s", PACKAGE_NAME, this.getClass().getSimpleName()),
+                String.format(phase + PHASE_FORMAT_SUFFIX, mInstallType, mStaged, mEnableRollback)))
+                .isTrue();
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/LauncherRule.java b/hostsidetests/install/src/android/cts/install/host/LauncherRule.java
new file mode 100644
index 0000000..fefe71e
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/LauncherRule.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.rules.ExternalResource;
+
+/**
+ * Changes default launcher with {@link #mTestLauncher} while testing. Restores to default
+ * launcher after test finished.
+ */
+final class LauncherRule extends ExternalResource {
+    private final BaseHostJUnit4Test mHostTest;
+    private final String mTestLauncher;
+
+    /**
+     * This value will be used to reset the default launcher to its correct component upon test
+     * completion.
+     */
+    private String mDefaultLauncher = null;
+
+    /**
+     * Constructs {@link LauncherRule} instance.
+     *
+     * @param hostTest Test to apply this rule.
+     * @param testLauncher Component name of customized launcher to set as default while testing.
+     */
+    LauncherRule(BaseHostJUnit4Test hostTest, String testLauncher) {
+        mHostTest = hostTest;
+        mTestLauncher = testLauncher;
+    }
+
+
+    protected void before() throws Throwable {
+        mDefaultLauncher = fetchDefaultLauncher();
+        setDefaultLauncher(mTestLauncher);
+    }
+
+    protected void after() {
+        try {
+            setDefaultLauncher(mDefaultLauncher);
+        } catch (DeviceNotAvailableException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /** Fetches the component name of the default launcher. Assert error if no launcher found. */
+    private String fetchDefaultLauncher() throws DeviceNotAvailableException {
+        final String PREFIX = "Launcher: ComponentInfo{";
+        final String POSTFIX = "}";
+        for (String s : mHostTest.getDevice().executeShellCommand(
+                "cmd shortcut get-default-launcher").split("\n")) {
+            if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) {
+                return s.substring(PREFIX.length(), s.length() - POSTFIX.length());
+            }
+        }
+        throw new AssertionError("No default launcher found");
+    }
+
+    /**
+     * Set the default launcher to a given component.
+     * If set to the broadcast receiver component of this test app, this will allow the test app to
+     * receive SESSION_COMMITTED broadcasts.
+     */
+    private void setDefaultLauncher(String launcherComponent) throws DeviceNotAvailableException {
+        assertThat(launcherComponent).isNotEmpty();
+        mHostTest.getDevice().executeShellCommand(
+                "cmd package set-home-activity " + launcherComponent);
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/SamegradeTest.java b/hostsidetests/install/src/android/cts/install/host/SamegradeTest.java
new file mode 100644
index 0000000..3c67a10
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/SamegradeTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.INSTALL_TYPE;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(DeviceParameterized.class)
+@UseParametersRunnerFactory(DeviceParameterized.RunnerFactory.class)
+public final class SamegradeTest extends BaseHostJUnit4Test {
+    private static final String PACKAGE_NAME = "android.cts.install";
+    private static final String PHASE_FORMAT_SUFFIX = "[%s_Staged%b_Rollback%b]";
+    private static final String ARRANGE_PHASE = "arrange_phase";
+    private static final String ASSERT_POST_ARRANGE_PHASE = "assert_postArrange_phase";
+    private static final String ACTION_PHASE = "action_phase";
+    private static final String ACTION_SYSTEMAPEX_PHASE = "action_systemApex_phase";
+    private static final String ASSERT_PRE_REBOOT_PHASE = "assert_preReboot_phase";
+    private static final String ASSERT_POST_REBOOT_PHASE = "assert_postReboot_phase";
+    private static final String ASSERT_SYSTEMAPEX_REBOOT_PHASE =
+            "assert_systemApex_postReboot_phase";
+    private static final String ASSERT_PHASE = "assert_phase";
+    private static final String CLEAN_UP_PHASE = "cleanUp_phase";
+
+    @Rule
+    public ShimApexRule mShimApexRule = new ShimApexRule(this);
+
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Rollback{1}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean enableRollback : booleanValues) {
+                temp.add(new Object[]{installType, enableRollback});
+            }
+        }
+        return temp;
+    }
+
+    @Before
+    @After
+    public void cleanUp() throws Exception {
+        runPhase(CLEAN_UP_PHASE);
+    }
+
+    @Before
+    public void assumeApexSupported() throws DeviceNotAvailableException {
+        if (mInstallType.containsApex()) {
+            assumeTrue("Device does not support updating APEX",
+                    mShimApexRule.isUpdatingApexSupported());
+        }
+    }
+
+    /**
+     * Samegrading on a non-APEX install type should be success.
+     */
+    @Test
+    public void testNonStagedSamegrade() throws Exception {
+        // Apex should not be committed in non-staged install, such logic covered in InstallTest.
+        assumeFalse(mInstallType.containsApex());
+
+        runPhase(ARRANGE_PHASE);
+        runPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runPhase(ACTION_PHASE);
+
+        runPhase(ASSERT_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedSameGrade() throws Exception {
+        assumeTrue(mInstallType.containsApex());
+        runStagedPhase(ARRANGE_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runStagedPhase(ACTION_PHASE);
+
+        runStagedPhase(ASSERT_PRE_REBOOT_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_REBOOT_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedSamegradeSystemApex() throws Exception {
+        assumeTrue(mInstallType.containsApex());
+
+        runStagedPhase(ACTION_SYSTEMAPEX_PHASE);
+        getDevice().reboot();
+
+        runStagedPhase(ASSERT_SYSTEMAPEX_REBOOT_PHASE);
+    }
+
+    private void runPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, false /* staged */);
+    }
+
+    private void runStagedPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, true /* staged */);
+    }
+
+    /**
+     * Runs the given phase of a test with parameters by calling into the device.
+     * Throws an exception if the test phase fails.
+     * <p>
+     * For example, <code>runPhase("action_phase", true);</code>
+     */
+    private void runPhase(String phase, boolean staged) throws DeviceNotAvailableException {
+        assertThat(runDeviceTests(PACKAGE_NAME,
+                String.format("%s.%s", PACKAGE_NAME, this.getClass().getSimpleName()),
+                String.format(phase + PHASE_FORMAT_SUFFIX, mInstallType, staged, mEnableRollback)))
+                .isTrue();
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/ShimApexRule.java b/hostsidetests/install/src/android/cts/install/host/ShimApexRule.java
new file mode 100644
index 0000000..4641aaa
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/ShimApexRule.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install.host;
+
+import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.rules.ExternalResource;
+
+/**
+ * Clears shim Apex if needed before and after each test to prevent flaky and reduce
+ * reboot time.
+ */
+final class ShimApexRule extends ExternalResource {
+    private static final String TAG = ShimApexRule.class.getSimpleName();
+    private final BaseHostJUnit4Test mHostTest;
+
+    ShimApexRule(BaseHostJUnit4Test hostTest) {
+        mHostTest = hostTest;
+    }
+
+    @Override
+    protected void before() throws Throwable {
+        uninstallShimApexIfNecessary();
+    }
+
+    @Override
+    protected void after() {
+        try {
+            uninstallShimApexIfNecessary();
+        } catch (DeviceNotAvailableException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Uninstalls a shim apex only if it's latest version is installed on /data partition (i.e.
+     * it has a version higher than {@code 1}).
+     *
+     * <p>This is purely to optimize tests run time. Since uninstalling an apex requires a reboot,
+     * and only a small subset of tests successfully install an apex, this code avoids ~10
+     * unnecessary reboots.
+     */
+    private void uninstallShimApexIfNecessary() throws DeviceNotAvailableException {
+        if (!isUpdatingApexSupported()) {
+            // Device doesn't support updating apex. Nothing to uninstall.
+            return;
+        }
+        if (getShimApex().sourceDir.startsWith("/system")) {
+            // System version is active, nothing to uninstall.
+            return;
+        }
+        // Non system version is active, need to uninstall it and reboot the device.
+        Log.i(TAG, "Uninstalling shim apex");
+        final String errorMessage =
+                mHostTest.getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
+        if (errorMessage != null) {
+            Log.e(TAG, "Failed to uninstall " + SHIM_APEX_PACKAGE_NAME + " : " + errorMessage);
+            return;
+        }
+
+        mHostTest.getDevice().reboot();
+        ITestDevice.ApexInfo shim = getShimApex();
+        assertThat(shim.versionCode).isEqualTo(1L);
+        assertThat(shim.sourceDir).startsWith("/system");
+    }
+
+    boolean isUpdatingApexSupported() throws DeviceNotAvailableException {
+        final String updatable = mHostTest.getDevice().getProperty("ro.apex.updatable");
+        return updatable != null && updatable.equals("true");
+    }
+
+    private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException {
+        return mHostTest.getDevice().getActiveApexes().stream().filter(
+                apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME)).findAny().orElseThrow(
+                () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME));
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/UpgradeTest.java b/hostsidetests/install/src/android/cts/install/host/UpgradeTest.java
new file mode 100644
index 0000000..cf58863
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/UpgradeTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.INSTALL_TYPE;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(DeviceParameterized.class)
+@UseParametersRunnerFactory(DeviceParameterized.RunnerFactory.class)
+public final class UpgradeTest extends BaseHostJUnit4Test {
+    private static final String PACKAGE_NAME = "android.cts.install";
+    private static final String PHASE_FORMAT_SUFFIX = "[%s_Staged%b_Rollback%b]";
+    private static final String ARRANGE_PHASE = "arrange_phase";
+    private static final String ASSERT_POST_ARRANGE_PHASE = "assert_postArrange_phase";
+    private static final String ACTION_PHASE = "action_phase";
+    private static final String ASSERT_PHASE = "assert_phase";
+    private static final String ASSERT_PRE_REBOOT_PHASE = "assert_preReboot_phase";
+    private static final String ASSERT_POST_REBOOT_PHASE = "assert_postReboot_phase";
+    private static final String CLEAN_UP_PHASE = "cleanUp_phase";
+
+    @Rule
+    public ShimApexRule mShimApexRule = new ShimApexRule(this);
+
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Rollback{1}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean enableRollback : booleanValues) {
+                temp.add(new Object[]{installType, enableRollback});
+            }
+        }
+        return temp;
+    }
+
+    @Before
+    @After
+    public void cleanUp() throws Exception {
+        runPhase(CLEAN_UP_PHASE);
+    }
+
+    @Before
+    public void assumeApexSupported() throws DeviceNotAvailableException {
+        if (mInstallType.containsApex()) {
+            assumeTrue("Device does not support updating APEX",
+                    mShimApexRule.isUpdatingApexSupported());
+        }
+    }
+
+    @Test
+    public void testNonStagedUpgrade() throws Exception {
+        // Apex should not be committed in non-staged install, such logic covered in InstallTest.
+        assumeFalse(mInstallType.containsApex());
+        runPhase(ARRANGE_PHASE);
+        runPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runPhase(ACTION_PHASE);
+
+        runPhase(ASSERT_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedUpgrade() throws Exception {
+        assumeTrue(mInstallType.containsApex());
+        runStagedPhase(ARRANGE_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runStagedPhase(ACTION_PHASE);
+
+        runStagedPhase(ASSERT_PRE_REBOOT_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_REBOOT_PHASE);
+    }
+
+    private void runPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, false /* staged */);
+    }
+
+    private void runStagedPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, true /* staged */);
+    }
+
+    /**
+     * Runs the given phase of a test with parameters by calling into the device.
+     * Throws an exception if the test phase fails.
+     * <p>
+     * For example, <code>runPhase("action_phase", true);</code>
+     */
+    private void runPhase(String phase, boolean staged) throws DeviceNotAvailableException {
+        assertThat(runDeviceTests(PACKAGE_NAME,
+                String.format("%s.%s", PACKAGE_NAME, this.getClass().getSimpleName()),
+                String.format(phase + PHASE_FORMAT_SUFFIX, mInstallType, staged, mEnableRollback)))
+                .isTrue();
+    }
+}
diff --git a/hostsidetests/jdwptunnel/sampleapps/debuggableapp/AndroidManifest.xml b/hostsidetests/jdwptunnel/sampleapps/debuggableapp/AndroidManifest.xml
index 2e2d2dd..3d85039 100755
--- a/hostsidetests/jdwptunnel/sampleapps/debuggableapp/AndroidManifest.xml
+++ b/hostsidetests/jdwptunnel/sampleapps/debuggableapp/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.jdwptunnel.sampleapp.debuggable">
+     package="android.jdwptunnel.sampleapp.debuggable">
 
     <application android:debuggable="true">
-        <activity android:name=".DebuggableSampleDeviceActivity" >
+        <activity android:name=".DebuggableSampleDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/jdwptunnel/sampleapps/profileableapp/AndroidManifest.xml b/hostsidetests/jdwptunnel/sampleapps/profileableapp/AndroidManifest.xml
index eb73f5d..678e60f 100755
--- a/hostsidetests/jdwptunnel/sampleapps/profileableapp/AndroidManifest.xml
+++ b/hostsidetests/jdwptunnel/sampleapps/profileableapp/AndroidManifest.xml
@@ -16,17 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.jdwptunnel.sampleapp.profileable">
+     package="android.jdwptunnel.sampleapp.profileable">
 
     <application android:debuggable="false">
-        <activity android:name=".ProfileableSampleDeviceActivity" >
+        <activity android:name=".ProfileableSampleDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-       <profileable android:shell="true" />
+       <profileable android:shell="true"/>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/library/Android.bp b/hostsidetests/library/Android.bp
new file mode 100644
index 0000000..e08ceed
--- /dev/null
+++ b/hostsidetests/library/Android.bp
@@ -0,0 +1,107 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "CtsUsesNativeLibraryTest",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+    java_resource_dirs: ["res"],
+    data: [":CtsUesNativeLibraryBuildPackage"],
+}
+
+// Note that this app is built as a java library. The actual app is built
+// by the test (CtsUsesNativeLibraryTest) while the test is running.
+// This java library is appended to the built apk by the test.
+java_library {
+    name: "CtsUsesNativeLibraryTestApp",
+    srcs: ["src_target/**/*.java"],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "android-support-test",
+        "compatibility-device-util-axt",
+    ],
+    sdk_version: "test_current",
+    compile_dex: true,
+    installable: false,
+    visibility: ["//visibility:private"],
+}
+
+// These are collection of tools and libraries that are required to build
+// an apk by the test. This zip file is extracted by the test and files
+// in the zip are executed from there.
+//
+// There are two tricks used here: 1) host tools such as aapt2 are listed
+// in the `tools` property although they technically are inputs of the zip,
+// not the tools for creating the zip. However, since the java test is not
+// specific to arch, it can't (transitively) depend on arch-specific (x86)
+// host tools. To work-around the problem, they are listed in the `tools`
+// property, and then used as inputs in the `cmd`.
+//
+// 2) signapk and libconscrypt_openjdk_jni are listed in the `host_required`
+// property instead of `tools` or `srcs`. This is because those modules are
+// neither specific to arch (thus can't be in tools), nor provide source (thus
+// can't be in srcs). To access them, their location in the soong intermediate
+// directory is manually searched in the cmd, while dependencies to them are
+// created using the `required` property.
+genrule {
+    name: "CtsUesNativeLibraryBuildPackage",
+    // srcs, tools, required are all "essentially" inputs of the zip
+    // (except for soong_zip which is actually the tool)
+    srcs: [
+        ":CtsUsesNativeLibraryTestApp",
+        ":sdk_public_30_android",
+        "testkey.pk8",
+        "testkey.x509.pem",
+    ],
+    tools: [
+        "aapt2",
+        "soong_zip",
+        "merge_zips",
+        // To make signapk.jar be generated under HOST_SOONG_OUT before this rule runes
+        "signapk",
+    ],
+    host_required: [
+        "signapk",
+        "libconscrypt_openjdk_jni",
+    ],
+    out: ["CtsUesNativeLibraryBuildPackage.zip"],
+    // Copied from system/apex/apexer/Android.bp
+    cmd: "HOST_OUT_BIN=$$(dirname $(location soong_zip)) && " +
+         "HOST_SOONG_OUT=$$(dirname $$(dirname $$HOST_OUT_BIN)) && " +
+         "SIGNAPK_JAR=$$(find $$HOST_SOONG_OUT -name \"signapk*\") && " +
+         "LIBCONSCRYPT_OPENJDK_JNI=$$(find $$HOST_SOONG_OUT -name \"libconscrypt_openjdk_jni.*\") && " +
+         "rm -rf $(genDir)/content && " +
+         "mkdir -p $(genDir)/content && " +
+         "cp $(location aapt2) $(genDir)/content && " +
+         "cp $(location merge_zips) $(genDir)/content && " +
+         "cp $(location :sdk_public_30_android) $(genDir)/content && " +
+         "cp $(location :CtsUsesNativeLibraryTestApp) $(genDir)/content && " +
+         "cp $(location testkey.pk8) $(genDir)/content && " +
+         "cp $(location testkey.x509.pem) $(genDir)/content && " +
+         "cp $$SIGNAPK_JAR $(genDir)/content && " +
+         "cp $$LIBCONSCRYPT_OPENJDK_JNI $(genDir)/content && " +
+         "$(location soong_zip) -C $(genDir)/content -D $(genDir)/content -o $(out) && " +
+         "rm -rf $(genDir)/content ",
+    visibility: ["//visibility:private"],
+}
diff --git a/hostsidetests/library/AndroidTest.xml b/hostsidetests/library/AndroidTest.xml
new file mode 100644
index 0000000..bd7119c
--- /dev/null
+++ b/hostsidetests/library/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS uses-native-library tests">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsUsesNativeLibraryTest.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/library/OWNERS b/hostsidetests/library/OWNERS
new file mode 100644
index 0000000..ac8666c
--- /dev/null
+++ b/hostsidetests/library/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 87896
+jiyong@google.com
+
diff --git a/hostsidetests/library/res/AndroidManifest_template.xml b/hostsidetests/library/res/AndroidManifest_template.xml
new file mode 100644
index 0000000..ee0336f
--- /dev/null
+++ b/hostsidetests/library/res/AndroidManifest_template.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This file is a template. Strings surrounded by % characters are to be replaced -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.test.usesnativesharedlibrary">
+
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="%TARGET_SDK_VERSION%" />
+
+    <application>
+        <!-- This java library is required for the test itself -->
+        <uses-library android:name="android.test.runner" />
+        <!-- Dependencies to the native shared libraries come here -->
+        %USES_LIBRARY%
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.test.usesnativesharedlibrary"
+                     android:label="NativeLibraryLoadTest" />
+</manifest>
diff --git a/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java b/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java
new file mode 100644
index 0000000..9030b2c
--- /dev/null
+++ b/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.appmanifest.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import com.android.tradefed.device.IFileEntry;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.ZipUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.BufferedWriter;
+import java.io.BufferedReader;
+import java.io.FileWriter;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipFile;
+
+/**
+ * Tests about uses-native-library tags that was introduced in Android S.
+ *
+ * The test reads the list of partner-defined public native shared libraries
+ * (see <a href="https://source.android.com/devices/tech/config/namespaces_libraries#adding-additional-native-libraries)">
+ * Adding additional native libraries</a>) and make sure that those are available to the apps
+ * only when they are explicitly listed on the app manifest using the new tag. The libs not listed
+ * are not available even though they are declared as public.
+ *
+ * This test also make sure that the new behavior is only for the new apps targeting Android S or
+ * higher. Apps targeting Android 11 or lower still has access to all partner-defined public libs
+ * regardless of the use of the tag.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class UsesNativeLibraryTestCase extends BaseHostJUnit4Test {
+    // The list of partner-defined public native shared libraries
+    private final Set<String> mPublicLibraries = new HashSet<>();
+
+    // The list of public libs that we will make the test app to depend on
+    private final Set<String> mSomePublicLibraries = new HashSet<>();
+
+    // Remaining public libraries that shouldn't be available to new apps
+    private final Set<String> mRemainingPublicLibraries = new HashSet<>();
+
+    private File mWorkDir;
+
+    // Name of a fake library that doesn't exist on the device
+    private String mNonExistingLib;
+
+    // Name of a library that actually exists on the device, but is not part of the public libraries
+    private String mPrivateLib;
+
+    @Before
+    public void setUp() throws Exception {
+        // extract "foo.so" from lines of foo.so ->  (so) foo.so
+        Pattern pattern = Pattern.compile("(\\S+)\\s*->\\s*\\((\\S+)\\)\\s*(\\S+)");
+        Arrays.stream(executeShellCommand("dumpsys package libraries").split("\n")).
+                skip(1) /* for "Libraries:" header */ .
+                map(line -> pattern.matcher(line.trim())).
+                filter(matcher -> matcher.matches() && matcher.group(2).equals("so")).
+                map(matcher -> matcher.group(1)).
+                forEach(mPublicLibraries::add);
+
+        // Pick first half of the public libraries
+        mPublicLibraries.stream().
+                limit(mPublicLibraries.size() / 2).
+                forEach(mSomePublicLibraries::add);
+
+        // ... and remainders
+        mPublicLibraries.stream().
+                filter(lib -> !mSomePublicLibraries.contains(lib)).
+                forEach(mRemainingPublicLibraries::add);
+
+        mNonExistingLib = "libnamethatneverexist.so";
+        assertFalse(mPublicLibraries.contains(mNonExistingLib)); // unlikely!
+
+        mPrivateLib = "libui.so"; // randomly chosen private lib
+        assertTrue(getDevice().getFileEntry("/system/lib/" + mPrivateLib) != null ||
+                   getDevice().getFileEntry("/system/lib64/" + mPrivateLib) != null);
+        assertFalse(mPublicLibraries.contains(mPrivateLib));
+
+        // The zip file contains all the tools and files for building a test app on demand. Extract
+        // it to the work directory.
+        try (ZipFile packageZip = new ZipFile(getTestInformation().getDependencyFile(
+                    "CtsUesNativeLibraryBuildPackage.zip", false))) {
+            mWorkDir = FileUtil.createTempDir("work");
+            ZipUtil.extractZip(packageZip, mWorkDir);
+
+            // Make sure executables are executable
+            FileUtil.chmod(getFile("aapt2"), "u+x");
+            FileUtil.chmod(getFile("merge_zips"), "u+x");
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @After
+    public void cleanUp() {
+        FileUtil.recursiveDelete(mWorkDir);
+    }
+
+    private File getFile(String path) {
+        return new File(mWorkDir, path);
+    }
+
+    private String executeShellCommand(String command) {
+        try {
+            return getDevice().executeShellCommand(command);
+        } catch (DeviceNotAvailableException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Stream<IFileEntry> getFileEntriesUnder(String path) {
+        try {
+            return getDevice().getFileEntry(path).getChildren(true).stream();
+        } catch (DeviceNotAvailableException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Stream<String> findPublicLibraryFilesUnder(String partition) {
+        return getFileEntriesUnder(partition + "/etc").
+                filter(fe -> {
+                    // For vendor partition we only allow public.libraries.txt file.
+                    // For other partitions, partner-added libs can be listed in
+                    // public.libraries-<companyname>.txt files.
+                    if (partition.equals("/vendor")) {
+                        return fe.getName().equals("public.libraries.txt");
+                    } else {
+                        return fe.getName().startsWith("public.libraries-") &&
+                                fe.getName().endsWith(".txt");
+                    }
+                }).
+                map(fe -> fe.getFullPath());
+    }
+
+    /**
+     * Tests if the native shared library list reported by the package manager is the same as
+     * the public.libraries*.txt files in the partitions.
+     */
+    @Test
+    public void testPublicLibrariesAreAllRegistered() throws DeviceNotAvailableException {
+        Set<String> libraryNamesFromTxt =
+                Stream.of("/system", "/system_ext", "/product", "/vendor").
+                flatMap(dir -> findPublicLibraryFilesUnder(dir)).
+                map(file -> executeShellCommand("cat " + file)).
+                flatMap(lines -> Arrays.stream(lines.split("\n"))).
+                filter(line -> {
+                    // filter-out empty lines or comment lines that start with #
+                    String strip = line.trim();
+                    return !strip.isEmpty() && !strip.startsWith("#");
+                }).
+                // line format is "name [bitness]". Extract the name part.
+                map(line -> line.trim().split("\\s+")[0]).
+                collect(Collectors.toSet());
+
+        assertEquals(mPublicLibraries, libraryNamesFromTxt);
+    }
+
+    /**
+     * Creates an AndroidManifest.xml file from the template with the given api level and the list
+     * of mandatory and optional native shared libraries
+     */
+    private File createManifestFileWithUsesNativeLibraryTags(File dir, int apiLevel,
+            String[] requiredLibraries, String[] optionalLibraries) {
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    getClass().getClassLoader().
+                    getResourceAsStream("AndroidManifest_template.xml")))) {
+            StringBuffer sb = new StringBuffer();
+            String line = null;
+            while( (line = reader.readLine()) != null) {
+                sb.append(line);
+            }
+            String template = sb.toString();
+
+            sb = new StringBuffer();
+            for(String lib : requiredLibraries) {
+                sb.append(String.format(
+                        "<uses-native-library android:name=\"%s\"/>", lib));
+            }
+            for(String lib : optionalLibraries) {
+                sb.append(String.format(
+                        "<uses-native-library android:name=\"%s\" android:required=\"false\"/>",
+                        lib));
+            }
+
+            String newContent = template.replace("%USES_LIBRARY%", sb.toString());
+            newContent = newContent.replace("%TARGET_SDK_VERSION%", Integer.toString(apiLevel));
+
+            File output = new File(dir, "AndroidManifest.xml");
+            FileUtil.writeToFile(newContent, output);
+            return output;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void runCommand(String cmd) {
+        CommandResult result = RunUtil.getDefault().runTimedCmd(100000, cmd.split(" "));
+        if (result.getExitCode() != 0) {
+            throw new RuntimeException(result.getStderr());
+        }
+    }
+
+    private File buildTestApp(int apiLevel,
+            String[] requiredLibraries,
+            String[] optionalLibraries,
+            String[] availableLibraries,
+            String[] unavailableLibraries) throws IOException {
+        File buildRoot = FileUtil.createTempDir("appbuild", mWorkDir);
+
+        // Create available.txt and unavailable.txt files. They contain the list of native libs
+        // that must be loadable and non-loadable. The Test app will fail if any of the lib in
+        // available.txt is non-loadable, or if any of the lib in unavailable.txt is loadable.
+        File assetDir = new File(buildRoot, "asset");
+        assetDir.mkdir();
+        File availableTxtFile = new File(assetDir, "available.txt");
+        File unavailableTxtFile = new File(assetDir, "unavailable.txt");
+        FileUtil.writeToFile(String.join("\n", availableLibraries), availableTxtFile, false);
+        FileUtil.writeToFile(String.join("\n", unavailableLibraries), unavailableTxtFile, false);
+
+        File manifestFile = createManifestFileWithUsesNativeLibraryTags(buildRoot, apiLevel,
+                requiredLibraries, optionalLibraries);
+
+        File resFile = new File(buildRoot, "package-res.apk");
+        runCommand(String.format("%s link --manifest %s -I %s -A %s -o %s",
+                getFile("aapt2"),
+                manifestFile,
+                getFile("android.jar"),
+                assetDir,
+                resFile));
+
+        // Append the app code to the apk
+        File unsignedApkFile = new File(buildRoot, "unsigned.apk");
+        runCommand(String.format("%s %s %s %s",
+                getFile("merge_zips"),
+                unsignedApkFile,
+                resFile,
+                getFile("CtsUsesNativeLibraryTestApp.jar")));
+
+        File signedApkFile = new File(buildRoot, "signed.apk");
+        runCommand(String.format("java -Djava.library.path=%s -jar %s %s %s %s %s",
+                mWorkDir,
+                getFile("signapk.jar"),
+                getFile("testkey.x509.pem"),
+                getFile("testkey.pk8"),
+                unsignedApkFile,
+                signedApkFile));
+
+        return signedApkFile;
+    }
+
+    private boolean installTestApp(File testApp) throws Exception {
+        // Explicit uninstallation is required because we might downgrade the target API level
+        // from 31 to 30
+        uninstallPackage("com.android.test.usesnativesharedlibrary");
+        try {
+            installPackage(testApp.toString());
+            return true;
+        } catch (TargetSetupError e) {
+            System.out.println(e.getMessage());
+            return false;
+        }
+    }
+
+    private void runInstalledTestApp() throws Exception {
+        runDeviceTests("com.android.test.usesnativesharedlibrary",
+                "com.android.test.usesnativesharedlibrary.LoadTest");
+    }
+
+    private static String[] add(Set<String> s, String...extra) {
+        List<String> ret = new ArrayList<>();
+        ret.addAll(s);
+        ret.addAll(Arrays.asList(extra));
+        return ret.toArray(new String[0]);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests for when apps depend on non-existing lib
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    public void testOldAppDependsOnNonExistingLib() throws Exception {
+        String[] requiredLibs = {mNonExistingLib};
+        String[] optionalLibs = {};
+        String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppDependsOnNonExistingLib() throws Exception {
+        String[] requiredLibs = {mNonExistingLib};
+        String[] optionalLibs = {};
+        String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+        String[] unavailableLibs = add(mPublicLibraries, mNonExistingLib, mPrivateLib);
+
+        assertFalse(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+
+        // install failed, so can't run the on-device test
+    }
+
+    @Test
+    public void testOldAppOptionallyDependsOnNonExistingLib() throws Exception {
+        String[] requiredLibs = {};
+        String[] optionalLibs = {mNonExistingLib};
+        String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppOptionallyDependsOnNonExistingLib() throws Exception {
+        String[] requiredLibs = {};
+        String[] optionalLibs = {mNonExistingLib};
+        String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests for when apps depend on private lib
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    public void testOldAppDependsOnPrivateLib() throws Exception {
+        String[] requiredLibs = {mPrivateLib};
+        String[] optionalLibs = {};
+        String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+        String[] unavailableLibs = {mPrivateLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppDependsOnPrivateLib() throws Exception {
+        String[] requiredLibs = {mPrivateLib};
+        String[] optionalLibs = {};
+        String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+        String[] unavailableLibs = add(mPublicLibraries, mPrivateLib, mPrivateLib);
+
+        assertFalse(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+
+        // install failed, so can't run the on-device test
+    }
+
+    @Test
+    public void testOldAppOptionallyDependsOnPrivateLib() throws Exception {
+        String[] requiredLibs = {};
+        String[] optionalLibs = {mPrivateLib};
+        String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+        String[] unavailableLibs = {mPrivateLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppOptionallyDependsOnPrivateLib() throws Exception {
+        String[] requiredLibs = {};
+        String[] optionalLibs = {mPrivateLib};
+        String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+        String[] unavailableLibs = {mPrivateLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests for when apps depend on all public libraries
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    public void testOldAppDependsOnAllPublicLibraries() throws Exception {
+        String[] requiredLibs = add(mPublicLibraries);
+        String[] optionalLibs = {};
+        String[] availableLibs = add(mPublicLibraries); // old app still has access to all libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppDependsOnAllPublicLibraries() throws Exception {
+        String[] requiredLibs = add(mPublicLibraries);
+        String[] optionalLibs = {};
+        String[] availableLibs = add(mPublicLibraries); // new app now has access to all libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests for when apps depend on some public libraries
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    public void testOldAppDependsOnSomePublicLibraries() throws Exception {
+        // select the first half of the public lib
+        String[] requiredLibs = add(mSomePublicLibraries);
+        String[] optionalLibs = {};
+        String[] availableLibs = add(mPublicLibraries); // old app still has access to all libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppDependsOnSomePublicLibraries() throws Exception {
+        String[] requiredLibs = add(mSomePublicLibraries);
+        String[] optionalLibs = {};
+        // new app has access to the listed libs only
+        String[] availableLibs = add(mSomePublicLibraries);
+        // And doesn't have access to the remaining public libs and of course non-existing
+        // and private libs.
+        String[] unavailableLibs = add(mRemainingPublicLibraries, mNonExistingLib, mPrivateLib);
+
+        assertTrue(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppOptionallyDependsOnSomePublicLibraries() throws Exception {
+        // select the first half of the public lib
+        String[] requiredLibs = {};
+        String[] optionalLibs = add(mSomePublicLibraries);
+        // new app has access to the listed libs only
+        String[] availableLibs = add(mSomePublicLibraries);
+        // And doesn't have access to the remaining public libs and of course non-existing
+        // and private libs.
+        String[] unavailableLibs = add(mRemainingPublicLibraries, mNonExistingLib, mPrivateLib);
+
+        assertTrue(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+}
+
diff --git a/hostsidetests/library/src_target/com/android/test/usesnativesharedlibrary/LoadTest.java b/hostsidetests/library/src_target/com/android/test/usesnativesharedlibrary/LoadTest.java
new file mode 100644
index 0000000..c4af778
--- /dev/null
+++ b/hostsidetests/library/src_target/com/android/test/usesnativesharedlibrary/LoadTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.usesnativesharedlibrary;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+import android.os.Build;
+import com.android.compatibility.common.util.PropertyUtil;
+
+import androidx.test.core.app.ApplicationProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+/**
+ * Tests if native shared libs are loadable or un-loadable as expected. The list of loadable libs is
+ * in the asset file <code>available.txt</code> and the list of un-loadable libs is in the asset
+ * file <code>unavailable.txt</code>. The files are dynamically created by the host-side test
+ * <code>UsesNativeLibraryTestCase</code>.
+ */
+@RunWith(JUnit4.class)
+public class LoadTest {
+    private List<String> libNamesFromAssetFile(String filename) {
+        List<String> result = new ArrayList<>();
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+                ApplicationProvider.getApplicationContext().getAssets().open(filename)))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (!line.isEmpty() && line.startsWith("lib") && line.endsWith(".so")) {
+                    // libfoo.so -> foo because that's what System.loadLibrary accepts
+                    result.add(line.substring(3, line.length()-3));
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return result;
+    }
+
+    private Set<String> vendorPublicLibraries() {
+        try (Stream<String> lines = Files.lines(Paths.get("/vendor/etc/public.libraries.txt"))) {
+            return lines.
+                filter(line -> {
+                    // filter-out empty lines or comment lines that start with #
+                    String strip = line.trim();
+                    return !strip.isEmpty() && !strip.startsWith("#");
+                }).
+                // line format is "name [bitness]". Extract the name part.
+                map(line -> line.trim().split("\\s+")[0]).
+                collect(Collectors.toSet());
+        } catch (IOException e) {
+            return Collections.emptySet();
+        }
+    }
+
+    /**
+     * Tests if libs listed in available.txt are all loadable
+     */
+    @Test
+    public void testAvailableLibrariesAreLoaded() {
+        List<String> unexpected = new ArrayList<>();
+        for (String lib : libNamesFromAssetFile("available.txt")) {
+            try {
+                System.loadLibrary(lib);
+            } catch (Throwable t) {
+                if (!PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.R)) {
+                    // Some old vendor.img might have stable entries in ./etc/public.libraries.txt
+                    // Don't emit error in that case.
+                    String libName = "lib" + lib + ".so";
+                    boolean notFound = t.getMessage().equals("dlopen failed: library \"" + libName
+                            + "\" not found");
+                    boolean isVendorPublicLib = vendorPublicLibraries().contains(libName);
+                    if (isVendorPublicLib && notFound) {
+                        continue;
+                    }
+                }
+                unexpected.add(t.getMessage());
+            }
+        };
+        assertThat("Some libraries failed to load", unexpected, is(Collections.emptyList()));
+    }
+
+    /**
+     * Tests if libs listed in unavailable.txt are all non-loadable
+     */
+    @Test
+    public void testUnavailableLibrariesAreNotLoaded() {
+        List<String> loadedLibs = new ArrayList<>();
+        List<String> unexpectedFailures = new ArrayList<>();
+        for (String lib : libNamesFromAssetFile("unavailable.txt")) {
+            try {
+                System.loadLibrary(lib);
+                loadedLibs.add("lib" + lib + ".so");
+            } catch (UnsatisfiedLinkError e) {
+                // This is expected
+            } catch (Throwable t) {
+                unexpectedFailures.add(t.getMessage());
+            }
+        };
+        assertThat("Some unavailable libraries were loaded", loadedLibs, is(Collections.emptyList()));
+        assertThat("Unexpected errors occurred", unexpectedFailures, is(Collections.emptyList()));
+    }
+}
diff --git a/hostsidetests/library/testkey.pk8 b/hostsidetests/library/testkey.pk8
new file mode 100644
index 0000000..586c1bd
--- /dev/null
+++ b/hostsidetests/library/testkey.pk8
Binary files differ
diff --git a/hostsidetests/library/testkey.x509.pem b/hostsidetests/library/testkey.x509.pem
new file mode 100644
index 0000000..e242d83
--- /dev/null
+++ b/hostsidetests/library/testkey.x509.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
+AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
+A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
+hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
+qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
+wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
+4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
+RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
+zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
+HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
+AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
+QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
+CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
+EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
+J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
+LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
++ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
+31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
+sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
+-----END CERTIFICATE-----
diff --git a/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml b/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
index 009fea8..3e5854d 100644
--- a/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
+++ b/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
@@ -15,28 +15,26 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.media.session.cts"
-    android:targetSandboxVersion="2">
+     package="android.media.session.cts"
+     android:targetSandboxVersion="2">
 
     <uses-sdk android:minSdkVersion="26"/>
 
-    <application
-        android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner"/>
 
-        <service
-            android:name=".MediaSessionManagerTest"
-            android:label="MediaSessionManagerTest"
-            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
+        <service android:name=".MediaSessionManagerTest"
+             android:label="MediaSessionManagerTest"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.media.session.cts"
-        android:label="MediaSession multi-user case CTS Tests" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.session.cts"
+         android:label="MediaSession multi-user case CTS Tests"/>
 
 </manifest>
diff --git a/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
index 6ed0e60..f12b823 100644
--- a/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
+++ b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
@@ -25,8 +25,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.media.session.MediaController;
-import android.media.session.MediaSessionManager;
 import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.media.session.MediaSessionManager;
 import android.os.Process;
 import android.service.notification.NotificationListenerService;
 
diff --git a/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml b/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml
index 70c48f9..a7270fc 100644
--- a/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml
+++ b/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml
@@ -16,16 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.media.app.media_session_test_helper"
-    android:versionCode="1"
-    android:versionName="1.0">
+     package="android.media.app.media_session_test_helper"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
     <application android:label="@string/label">
-        <service android:name=".MediaSessionTestHelperService">
+        <service android:name=".MediaSessionTestHelperService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.app.media_session_test_helper.ACTION_CONTROL" />
+                <action android:name="android.media.app.media_session_test_helper.ACTION_CONTROL"/>
             </intent-filter>
         </service>
     </application>
diff --git a/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml b/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
index efb1288..742a7c0 100644
--- a/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
+++ b/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
@@ -13,26 +13,27 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.monkey">
+     package="com.android.cts.monkey">
 
     <application android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
 
-        <activity
-            android:name=".MonkeyActivity"
-            android:screenOrientation="locked">
+        <activity android:name=".MonkeyActivity"
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name=".BaboonActivity"
-            android:screenOrientation="locked">
+        <activity android:name=".BaboonActivity"
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.MONKEY" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.MONKEY"/>
             </intent-filter>
         </activity>
 
diff --git a/hostsidetests/monkey/test-apps/CtsMonkeyApp2/AndroidManifest.xml b/hostsidetests/monkey/test-apps/CtsMonkeyApp2/AndroidManifest.xml
index d08e231..2ecb8f3 100644
--- a/hostsidetests/monkey/test-apps/CtsMonkeyApp2/AndroidManifest.xml
+++ b/hostsidetests/monkey/test-apps/CtsMonkeyApp2/AndroidManifest.xml
@@ -13,15 +13,17 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.monkey2">
+     package="com.android.cts.monkey2">
 
     <application android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
 
-        <activity android:name=".ChimpActivity">
+        <activity android:name=".ChimpActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
diff --git a/hostsidetests/multiuser/TEST_MAPPING b/hostsidetests/multiuser/TEST_MAPPING
new file mode 100644
index 0000000..592aaad
--- /dev/null
+++ b/hostsidetests/multiuser/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMultiUserHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java b/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java
index 07883e0..2833ddb 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java
@@ -15,14 +15,21 @@
  */
 package android.host.multiuser;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.IDeviceTest;
 
 import org.junit.After;
+import org.junit.AssumptionViolatedException;
 import org.junit.Before;
-
+import org.junit.Rule;
+import org.junit.rules.TestName;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -35,13 +42,11 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 /**
  * Base class for multi user tests.
  */
-public class BaseMultiUserTest implements IDeviceTest {
+// Must be public because of @Rule
+public abstract class BaseMultiUserTest implements IDeviceTest {
 
     /** Guest flag value from android/content/pm/UserInfo.java */
     private static final int FLAG_GUEST = 0x00000004;
@@ -52,26 +57,23 @@
      */
     private static final String FEATURE_AUTOMOTIVE = "feature:android.hardware.type.automotive";
 
-
     protected static final long LOGCAT_POLL_INTERVAL_MS = 1000;
     protected static final long USER_SWITCH_COMPLETE_TIMEOUT_MS = 360_000;
 
     /** Whether multi-user is supported. */
-    protected boolean mSupportsMultiUser;
-    protected boolean mIsSplitSystemUser;
     protected int mInitialUserId;
     protected int mPrimaryUserId;
 
     /** Users we shouldn't delete in the tests. */
     private ArrayList<Integer> mFixedUsers;
 
-    private ITestDevice mDevice;
+    protected ITestDevice mDevice;
+
+    @Rule
+    public final TestName mTestNameRule = new TestName();
 
     @Before
     public void setUp() throws Exception {
-        mSupportsMultiUser = getDevice().getMaxNumberOfUsersSupported() > 1;
-        mIsSplitSystemUser = checkIfSplitSystemUser();
-
         mInitialUserId = getDevice().getCurrentUser();
         mPrimaryUserId = getDevice().getPrimaryUserId();
 
@@ -81,8 +83,10 @@
 
     @After
     public void tearDown() throws Exception {
-        if (getDevice().getCurrentUser() != mInitialUserId) {
-            CLog.w("User changed during test. Switching back to " + mInitialUserId);
+        int currentUserId = getDevice().getCurrentUser();
+        if (currentUserId != mInitialUserId) {
+            CLog.w("User changed during test (to %d). Switching back to %d", currentUserId,
+                    mInitialUserId);
             getDevice().switchUser(mInitialUserId);
         }
         // Remove the users created during this test.
@@ -99,13 +103,23 @@
         return mDevice;
     }
 
+    protected String getTestName() {
+        return mTestNameRule.getMethodName();
+    }
+
+    protected void assumeNotRoot() throws DeviceNotAvailableException {
+        if (!getDevice().isAdbRoot()) return;
+
+        String message = "Cannot test " + getTestName() + " on rooted devices";
+        CLog.logAndDisplay(Log.LogLevel.WARN, message);
+        throw new AssumptionViolatedException(message);
+    }
+
     protected int createRestrictedProfile(int userId)
             throws DeviceNotAvailableException, IllegalStateException{
         final String command = "pm create-user --profileOf " + userId + " --restricted "
                 + "TestUser_" + System.currentTimeMillis();
-        CLog.d("Starting command: " + command);
         final String output = getDevice().executeShellCommand(command);
-        CLog.d("Output for command " + command + ": " + output);
 
         if (output.startsWith("Success")) {
             try {
@@ -119,29 +133,6 @@
         throw new IllegalStateException();
     }
 
-    /**
-     * @return the userid of the created user
-     */
-    protected int createUser()
-            throws DeviceNotAvailableException, IllegalStateException {
-        final String command = "pm create-user "
-                + "TestUser_" + System.currentTimeMillis();
-        CLog.d("Starting command: " + command);
-        final String output = getDevice().executeShellCommand(command);
-        CLog.d("Output for command " + command + ": " + output);
-
-        if (output.startsWith("Success")) {
-            try {
-                return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
-            } catch (NumberFormatException e) {
-                CLog.e("Failed to parse result: %s", output);
-            }
-        } else {
-            CLog.e("Failed to create user: %s", output);
-        }
-        throw new IllegalStateException();
-    }
-
     protected int createGuestUser() throws Exception {
         return mDevice.createUser(
                 "TestUser_" + System.currentTimeMillis() /* name */,
@@ -158,18 +149,20 @@
         return -1;
     }
 
-    protected boolean isAutomotiveDevice() throws Exception {
-        return getDevice().hasFeature(FEATURE_AUTOMOTIVE);
+    protected void assumeIsAutomotive() throws Exception {
+        assumeTrue("Device does not have " + FEATURE_AUTOMOTIVE,
+                getDevice().hasFeature(FEATURE_AUTOMOTIVE));
     }
 
     protected void assertSwitchToNewUser(int toUserId) throws Exception {
         final String exitString = "Finished processing BOOT_COMPLETED for u" + toUserId;
         final Set<String> appErrors = new LinkedHashSet<>();
         getDevice().executeAdbCommand("logcat", "-b", "all", "-c"); // Reset log
-        assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
+        assertWithMessage("Couldn't switch to user %s", toUserId)
+                .that(getDevice().switchUser(toUserId)).isTrue();
         final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
-        assertTrue("Didn't receive BOOT_COMPLETED delivered notification. appErrors="
-                + appErrors, result);
+        assertWithMessage("Didn't receive BOOT_COMPLETED delivered notification. appErrors=%s",
+                appErrors).that(result).isTrue();
         if (!appErrors.isEmpty()) {
             throw new AppCrashOnBootError(appErrors);
         }
@@ -179,17 +172,19 @@
         final String exitString = "uc_continue_user_switch: [" + fromUserId + "," + toUserId + "]";
         final Set<String> appErrors = new LinkedHashSet<>();
         getDevice().executeAdbCommand("logcat", "-b", "all", "-c"); // Reset log
-        assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
+        assertWithMessage("Couldn't switch to user %s", toUserId)
+                .that(getDevice().switchUser(toUserId)).isTrue();
         final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
-        assertTrue("Didn't reach \"Continue user switch\" stage. appErrors=" + appErrors, result);
+        assertWithMessage("Didn't reach \"Continue user switch\" stage. appErrors=%s", appErrors)
+                .that(result).isTrue();
         if (!appErrors.isEmpty()) {
             throw new AppCrashOnBootError(appErrors);
         }
     }
 
     protected void assertUserNotPresent(int userId) throws Exception {
-        assertFalse("User ID " + userId + " should not be present",
-                getDevice().listUsers().contains(userId));
+        assertWithMessage("User ID %s should not be present", userId)
+                .that(getDevice().listUsers()).doesNotContain(userId);
     }
 
     /*
@@ -243,7 +238,7 @@
             in.close();
             if (mExitFound) {
                 if (!appErrors.isEmpty()) {
-                    CLog.w("App crash dialogs found: " + appErrors);
+                    CLog.w("App crash dialogs found: %s", appErrors);
                 }
                 return true;
             }
@@ -260,14 +255,6 @@
         }
     }
 
-    private boolean checkIfSplitSystemUser() throws DeviceNotAvailableException {
-        final String commandOuput = getDevice().executeShellCommand(
-                "getprop ro.fw.system_user_split");
-        return "y".equals(commandOuput) || "yes".equals(commandOuput)
-                || "1".equals(commandOuput) || "true".equals(commandOuput)
-                || "on".equals(commandOuput);
-    }
-
     static class AppCrashOnBootError extends AssertionError {
         private static final Pattern PACKAGE_NAME_PATTERN = Pattern.compile("package ([^\\s]+)");
         private Set<String> errorPackages;
@@ -303,13 +290,15 @@
                 public void evaluate() throws Throwable {
                     Set<String> errors = evaluateAndReturnAppCrashes(base);
                     if (errors.isEmpty()) {
+                        CLog.v("Good News, Everyone! No App crashes on %s",
+                                description.getMethodName());
                         return;
                     }
-                    CLog.e("Retrying due to app crashes: " + errors);
+                    CLog.e("Retrying due to app crashes: %s", errors);
                     // Fail only if same apps are crashing in both runs
                     errors.retainAll(evaluateAndReturnAppCrashes(base));
-                    assertTrue("App error dialog(s) are present after 2 attempts: " + errors,
-                            errors.isEmpty());
+                    assertWithMessage("App error dialog(s) are present after 2 attempts")
+                            .that(errors).isEmpty();
                 }
             };
         }
@@ -323,4 +312,29 @@
             return new HashSet<>();
         }
     }
+
+    /**
+     * Rule that skips a test if device does not support more than 1 user
+     */
+    protected static class SupportsMultiUserRule implements TestRule {
+
+        private final IDeviceTest mDeviceTest;
+
+        SupportsMultiUserRule(IDeviceTest deviceTest) {
+            mDeviceTest = deviceTest;
+        }
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    boolean supports = mDeviceTest.getDevice().getMaxNumberOfUsersSupported() > 1;
+                    assumeTrue("device does not support multi users", supports);
+
+                    base.evaluate();
+                }
+            };
+        }
+    }
 }
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
index 1513232..dc81f47 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
@@ -18,29 +18,28 @@
 
 import android.platform.test.annotations.Presubmit;
 
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  * Test verifies that users can be created/switched to without error dialogs shown to the user
+ *
  * Run: atest CreateUsersNoAppCrashesTest
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CreateUsersNoAppCrashesTest extends BaseMultiUserTest {
+public final class CreateUsersNoAppCrashesTest extends BaseMultiUserTest {
+
+    @Rule
+    public final RuleChain mLookAllThoseRules = RuleChain.outerRule(new SupportsMultiUserRule(this))
+            .around(new AppCrashRetryRule());
 
     @Presubmit
     @Test
     public void testCanCreateGuestUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
         int userId = getDevice().createUser(
                 "TestUser_" + System.currentTimeMillis() /* name */,
                 true /* guest */,
@@ -52,9 +51,6 @@
     @Presubmit
     @Test
     public void testCanCreateSecondaryUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
         int userId = getDevice().createUser(
                 "TestUser_" + System.currentTimeMillis() /* name */,
                 false /* guest */,
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
index fc385b1..4f19d81 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
@@ -15,32 +15,30 @@
  */
 package android.host.multiuser;
 
-import static com.android.tradefed.log.LogUtil.CLog;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.host.multiuser.BaseMultiUserTest.SupportsMultiUserRule;
 
 import com.android.compatibility.common.util.CddTest;
-import com.android.ddmlib.Log;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
-import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CreateUsersPermissionTest extends BaseMultiUserTest {
+public final class CreateUsersPermissionTest extends BaseMultiUserTest {
+
+    @Rule
+    public final SupportsMultiUserRule mSupportsMultiUserRule = new SupportsMultiUserRule(this);
 
     @Test
     public void testCanCreateGuestUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
         createGuestUser();
     }
 
     @Test
     public void testCanCreateEphemeralUser() throws Exception {
-        if (!mSupportsMultiUser || !mIsSplitSystemUser) {
-            return;
-        }
         getDevice().createUser(
                 "TestUser_" + System.currentTimeMillis() /* name */,
                 false /* guest */,
@@ -49,33 +47,14 @@
 
     @Test
     public void testCanCreateRestrictedUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
         createRestrictedProfile(mPrimaryUserId);
     }
 
-    @Test
-    public void testCantSetUserRestriction() throws Exception {
-        if (getDevice().isAdbRoot()) {
-            CLog.logAndDisplay(Log.LogLevel.WARN,
-                    "Cannot test testCantSetUserRestriction on rooted devices");
-            return;
-        }
-        final String setRestriction = "pm set-user-restriction no_fun ";
-        final String output = getDevice().executeShellCommand(setRestriction + "1");
-        final boolean isErrorOutput = output.contains("SecurityException")
-                && output.contains("You need MANAGE_USERS permission");
-        Assert.assertTrue("Trying to set user restriction should fail with SecurityException. "
-                + "command output: " + output, isErrorOutput);
-    }
-
     @CddTest(requirement="9.5/A-1-3")
     @Test
     public void testCanCreateGuestUserWhenUserLimitReached() throws Exception {
-        if (!isAutomotiveDevice()) {
-            return;
-        }
+        assumeIsAutomotive();
+
         // Remove existing guest user
         int guestUserId = getGuestUser();
         if (guestUserId != -1) {
@@ -94,7 +73,7 @@
         }
         createGuestUser();
         userCount = getDevice().listUsers().size();
-        Assert.assertTrue("User count should be greater than max users due to added guest user",
-                userCount > maxUsers);
+        assertWithMessage("User count should be greater than max users due to added guest user")
+                .that(userCount).isGreaterThan(maxUsers);
     }
 }
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java b/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
index 1b53675..883118a 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
@@ -17,30 +17,28 @@
 
 import android.platform.test.annotations.Presubmit;
 
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  * Test verifies that ephemeral users are removed after switched away and after reboot.
+ *
  * Run: atest android.host.multiuser.EphemeralTest
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class EphemeralTest extends BaseMultiUserTest {
 
+    @Rule
+    public final SupportsMultiUserRule mSupportsMultiUserRule = new SupportsMultiUserRule(this);
+
     /** Test to verify ephemeral user is removed after switch out to another user. */
     @Presubmit
     @Test
     public void testSwitchAndRemoveEphemeralUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
         int ephemeralUserId = -1;
         try {
             ephemeralUserId = getDevice().createUser(
@@ -61,9 +59,6 @@
     @Presubmit
     @Test
     public void testRebootAndRemoveEphemeralUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
         int ephemeralUserId = -1;
         try {
             ephemeralUserId = getDevice().createUser(
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java b/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
index d4a6ef2..139a35c 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
@@ -15,29 +15,37 @@
  */
 package android.host.multiuser;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.host.multiuser.BaseMultiUserTest.SupportsMultiUserRule;
+
 import com.android.compatibility.common.util.CddTest;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
-import java.util.concurrent.TimeUnit;
-
-import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Run: atest SecondaryUsersTest
+ */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class SecondaryUsersTest extends BaseMultiUserTest {
+public final class SecondaryUsersTest extends BaseMultiUserTest {
 
     // Extra time to give the system to switch into secondary user after boot complete.
     private static final long SECONDARY_USER_BOOT_COMPLETE_TIMEOUT_MS = 30000;
 
     private static final long POLL_INTERVAL_MS = 100;
 
+    @Rule
+    public final SupportsMultiUserRule mSupportsMultiUserRule = new SupportsMultiUserRule(this);
+
     @CddTest(requirement="9.5/A-1-2")
     @Test
     public void testSwitchToSecondaryUserBeforeBootComplete() throws Exception {
-        if (!isAutomotiveDevice() || !mSupportsMultiUser) {
-            return;
-        }
+        assumeIsAutomotive();
 
         getDevice().nonBlockingReboot();
         getDevice().waitForBootComplete(TimeUnit.MINUTES.toMillis(2));
@@ -58,6 +66,7 @@
             }
             Thread.sleep(POLL_INTERVAL_MS);
         }
-        Assert.assertTrue("Must switch to secondary user before boot complete", isUserSecondary);
+        assertWithMessage("Must switch to secondary user before boot complete")
+                .that(isUserSecondary).isTrue();
     }
 }
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/SetUsersRestrictionsTest.java b/hostsidetests/multiuser/src/android/host/multiuser/SetUsersRestrictionsTest.java
new file mode 100644
index 0000000..b44915e
--- /dev/null
+++ b/hostsidetests/multiuser/src/android/host/multiuser/SetUsersRestrictionsTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.host.multiuser;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for user restrictions that DO NOT REQUIRE multi-user support.
+ *
+ * Run: atest SetUsersRestrictionsTest
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class SetUsersRestrictionsTest extends BaseMultiUserTest {
+
+    /**
+     * Tests that set-user-restriction is disabled on user builds.
+     */
+    @Test
+    public void testCantSetUserRestriction() throws Exception {
+        assumeNotRoot();
+
+        final String setRestriction = "pm set-user-restriction no_fun ";
+        final String output = getDevice().executeShellCommand(setRestriction + "1");
+        final boolean isErrorOutput = output.contains("SecurityException")
+                && output.contains("You need MANAGE_USERS permission");
+        assertWithMessage("Trying to set user restriction should fail with SecurityException. "
+                + "command output: %s", output).that(isErrorOutput).isTrue();
+    }
+}
diff --git a/hostsidetests/net/app/AndroidManifest.xml b/hostsidetests/net/app/AndroidManifest.xml
index 3940de4..e5bae5f 100644
--- a/hostsidetests/net/app/AndroidManifest.xml
+++ b/hostsidetests/net/app/AndroidManifest.xml
@@ -15,42 +15,42 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.net.hostside">
+     package="com.android.cts.net.hostside">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
 
-    <application android:requestLegacyExternalStorage="true" >
-        <uses-library android:name="android.test.runner" />
-        <activity android:name=".MyActivity" />
+    <application android:requestLegacyExternalStorage="true">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".MyActivity"/>
         <service android:name=".MyVpnService"
-                android:permission="android.permission.BIND_VPN_SERVICE">
+             android:permission="android.permission.BIND_VPN_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.net.VpnService"/>
             </intent-filter>
         </service>
-        <service
-            android:name=".MyNotificationListenerService"
-            android:label="MyNotificationListenerService"
-            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
+        <service android:name=".MyNotificationListenerService"
+             android:label="MyNotificationListenerService"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.net.hostside" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.net.hostside"/>
 
 </manifest>
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
index 6f32c56..e0ce4ea 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
@@ -101,7 +101,7 @@
     @Test
     public void testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction()
             throws Exception {
-        setPendingIntentWhitelistDuration(NETWORK_TIMEOUT_MS);
+        setPendingIntentAllowlistDuration(NETWORK_TIMEOUT_MS);
         try {
             registerNotificationListenerService();
             setDozeMode(true);
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 71f6f2f..0883b1a 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -25,7 +25,6 @@
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getContext;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getInstrumentation;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getWifiManager;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
 
@@ -46,15 +45,17 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
-import android.net.wifi.WifiManager;
 import android.os.BatteryManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.util.Log;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import org.junit.Rule;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
@@ -130,7 +131,6 @@
     protected int mUid;
     private int mMyUid;
     private MyServiceClient mServiceClient;
-    private String mDeviceIdleConstantsSetting;
 
     @Rule
     public final RuleChain mRuleChain = RuleChain.outerRule(new RequiredPropertiesRule())
@@ -147,7 +147,6 @@
         mMyUid = getUid(mContext.getPackageName());
         mServiceClient = new MyServiceClient(mContext);
         mServiceClient.bind();
-        mDeviceIdleConstantsSetting = "device_idle_constants";
         executeShellCommand("cmd netpolicy start-watching " + mUid);
         setAppIdle(false);
 
@@ -721,15 +720,18 @@
                 nm.isNotificationListenerAccessGranted(listenerComponent));
     }
 
-    protected void setPendingIntentWhitelistDuration(int durationMs) throws Exception {
-        executeSilentShellCommand(String.format(
-                "settings put global %s %s=%d", mDeviceIdleConstantsSetting,
-                "notification_whitelist_duration", durationMs));
+    protected void setPendingIntentAllowlistDuration(long durationMs) {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_DEVICE_IDLE, "notification_allowlist_duration_ms",
+                    String.valueOf(durationMs), /* makeDefault */ false);
+        });
     }
 
-    protected void resetDeviceIdleSettings() throws Exception {
-        executeShellCommand(String.format("settings delete global %s",
-                mDeviceIdleConstantsSetting));
+    protected void resetDeviceIdleSettings() {
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS,
+                        DeviceConfig.NAMESPACE_DEVICE_IDLE));
     }
 
     protected void launchComponentAndAssertNetworkAccess(int type) throws Exception {
diff --git a/hostsidetests/net/app2/AndroidManifest.xml b/hostsidetests/net/app2/AndroidManifest.xml
index ad270b3..eb777f2 100644
--- a/hostsidetests/net/app2/AndroidManifest.xml
+++ b/hostsidetests/net/app2/AndroidManifest.xml
@@ -16,38 +16,43 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.net.hostside.app2" >
+     package="com.android.cts.net.hostside.app2">
 
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <!--
-         This application is used to listen to RESTRICT_BACKGROUND_CHANGED intents and store
-         them in a shared preferences which is then read by the test app. These broadcasts are
-         handled by 2 listeners, one defined the manifest and another dynamically registered by
-         a service.
+                 This application is used to listen to RESTRICT_BACKGROUND_CHANGED intents and store
+                 them in a shared preferences which is then read by the test app. These broadcasts are
+                 handled by 2 listeners, one defined the manifest and another dynamically registered by
+                 a service.
 
-         The manifest-defined listener also handles ordered broadcasts used to share data with the
-         test app.
+                 The manifest-defined listener also handles ordered broadcasts used to share data with the
+                 test app.
 
-         This application also provides a service, RemoteSocketFactoryService, that the test app can
-         use to open sockets to remote hosts as a different user ID.
-    -->
+                 This application also provides a service, RemoteSocketFactoryService, that the test app can
+                 use to open sockets to remote hosts as a different user ID.
+            -->
     <application android:usesCleartextTraffic="true">
-        <activity android:name=".MyActivity" android:exported="true"/>
-        <service android:name=".MyService" android:exported="true"/>
-        <service android:name=".MyForegroundService" android:exported="true"/>
-        <service android:name=".RemoteSocketFactoryService" android:exported="true"/>
+        <activity android:name=".MyActivity"
+             android:exported="true"/>
+        <service android:name=".MyService"
+             android:exported="true"/>
+        <service android:name=".MyForegroundService"
+             android:exported="true"/>
+        <service android:name=".RemoteSocketFactoryService"
+             android:exported="true"/>
 
-        <receiver android:name=".MyBroadcastReceiver" >
+        <receiver android:name=".MyBroadcastReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED" />
-                <action android:name="com.android.cts.net.hostside.app2.action.GET_COUNTERS" />
-                <action android:name="com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS" />
-                <action android:name="com.android.cts.net.hostside.app2.action.CHECK_NETWORK" />
-                <action android:name="com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION" />
-                <action android:name="com.android.cts.net.hostside.app2.action.SHOW_TOAST" />
+                <action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED"/>
+                <action android:name="com.android.cts.net.hostside.app2.action.GET_COUNTERS"/>
+                <action android:name="com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS"/>
+                <action android:name="com.android.cts.net.hostside.app2.action.CHECK_NETWORK"/>
+                <action android:name="com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION"/>
+                <action android:name="com.android.cts.net.hostside.app2.action.SHOW_TOAST"/>
                 </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index ac28c7a..f681f36 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.net;
 
+import android.platform.test.annotations.FlakyTest;
+
 import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
@@ -146,6 +148,7 @@
                 "testBackgroundNetworkAccess_disabled");
     }
 
+    @FlakyTest(bugId=170180675)
     public void testAppIdleMetered_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
                 "testBackgroundNetworkAccess_whitelisted");
@@ -176,6 +179,7 @@
                 "testBackgroundNetworkAccess_disabled");
     }
 
+    @FlakyTest(bugId=170180675)
     public void testAppIdleNonMetered_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
                 "testBackgroundNetworkAccess_whitelisted");
diff --git a/hostsidetests/numberblocking/app/AndroidManifest.xml b/hostsidetests/numberblocking/app/AndroidManifest.xml
index 6ff2d9e..a327a34 100755
--- a/hostsidetests/numberblocking/app/AndroidManifest.xml
+++ b/hostsidetests/numberblocking/app/AndroidManifest.xml
@@ -15,27 +15,27 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.numberblocking.hostside">
+     package="com.android.cts.numberblocking.hostside">
 
     <uses-permission android:name="android.permission.CALL_PHONE"/>
     <uses-permission android:name="android.permission.READ_CALL_LOG"/>
     <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name=".CallBlockingTest$DummyConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.numberblocking.hostside"
-                     android:label="Number blocking CTS Tests"/>
+         android:targetPackage="com.android.cts.numberblocking.hostside"
+         android:label="Number blocking CTS Tests"/>
 
 </manifest>
-
diff --git a/hostsidetests/os/AndroidTest.xml b/hostsidetests/os/AndroidTest.xml
index 5729e43..1eb77ac 100644
--- a/hostsidetests/os/AndroidTest.xml
+++ b/hostsidetests/os/AndroidTest.xml
@@ -26,6 +26,7 @@
         <option name="test-file-name" value="CtsHostProcfsTestApp.apk" />
         <option name="test-file-name" value="CtsInattentiveSleepTestApp.apk" />
         <option name="test-file-name" value="CtsHostEnvironmentTestApp.apk" />
+        <option name="test-file-name" value="CtsStaticSharedLibTestApp.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsOsHostTestCases.jar" />
diff --git a/hostsidetests/os/src/android/os/cts/OsHostTests.java b/hostsidetests/os/src/android/os/cts/OsHostTests.java
index 8e3f5c5..4ce4d7b 100644
--- a/hostsidetests/os/src/android/os/cts/OsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/OsHostTests.java
@@ -154,14 +154,17 @@
             // Clean slate in case of earlier aborted run
             mDevice.uninstallPackage(HOST_VERIFICATION_PKG);
 
-            String[] options = { AbiUtils.createAbiFlag(mAbi.getName()) };
+            final String[] options = { AbiUtils.createAbiFlag(mAbi.getName()) };
+            final int currentUser = mDevice.getCurrentUser();
 
             mDevice.clearLogcat();
 
-            String installResult = getDevice().installPackage(getTestAppFile(HOST_VERIFICATION_APK),
-                    false /* = reinstall? */, options);
+            final String errorString;
+            errorString = mDevice.installPackageForUser(getTestAppFile(HOST_VERIFICATION_APK),
+                    false /* = reinstall? */, currentUser, options);
 
-            assertNull("Couldn't install web intent filter sample apk", installResult);
+            assertNull("Couldn't install web intent filter sample apk in user " +
+                    currentUser + " : " + errorString, errorString);
 
             String logs = mDevice.executeAdbCommand("logcat", "-v", "brief", "-d");
             boolean foundVerifierOutput = false;
diff --git a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
index 689a095..360d8d7 100644
--- a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
@@ -671,6 +671,23 @@
         }
     }
 
+    public void testSamegradeStaticSharedLibByAdb() throws Exception {
+        getDevice().uninstallPackage(STATIC_LIB_PROVIDER5_PKG);
+        try {
+            assertNull(install(STATIC_LIB_PROVIDER5_APK));
+            assertNull(install(STATIC_LIB_PROVIDER5_APK, true /*reinstall*/));
+        } finally {
+            getDevice().uninstallPackage(STATIC_LIB_PROVIDER5_PKG);
+        }
+    }
+
+    @AppModeFull(reason = "Instant app cannot get package installer service")
+    public void testCannotSamegradeStaticSharedLibByInstaller() throws Exception {
+        runDeviceTests("android.os.lib.app",
+                "android.os.lib.app.StaticSharedLibsTests",
+                "testSamegradeStaticSharedLibFail");
+    }
+
     private void runDeviceTests(String packageName, String testClassName,
             String testMethodName) throws DeviceNotAvailableException {
         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(packageName,
diff --git a/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml b/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
index 9480418..ace8c82 100644
--- a/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
@@ -13,28 +13,41 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
 <!-- Declare the contents of this Android application.  The namespace
      attribute brings in the Android platform namespace, and the package
      supplies a unique name for the application.  When writing your
      own application, the package name must be changed from "com.example.*"
      to come from a domain that you own or have control over. -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.openlinksskeleton"
-    android:versionCode="1"
-    android:versionName="1.0">
+     package="com.android.cts.openlinksskeleton"
+     android:versionCode="1"
+     android:versionName="1.0">
 
-    <application android:label="Open Links Skeleton" android:hasCode="false" >
+    <application android:label="Open Links Skeleton"
+         android:hasCode="false">
 
-        <activity android:name="DummyWebLinkActivity">
+        <activity android:name="DummyWebLinkActivity"
+             android:exported="true">
             <intent-filter android:autoVerify="true">
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="explicit.example.com" />
-                <data android:host="*.wildcard.tld" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="http"/>
+                <data android:scheme="https"/>
+                <data android:host="*.wildcard.tld"/>
+            </intent-filter>
+
+            <!-- Also make sure that verification picks up web navigation
+                                 handling even when the filter matches non-web schemes -->
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="http"/>
+                <data android:scheme="https"/>
+                <data android:scheme="nonweb"/>
+                <data android:host="explicit.example.com"/>
             </intent-filter>
         </activity>
 
diff --git a/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
index 487b8cc..3588a14 100755
--- a/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
@@ -16,10 +16,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.os.inattentivesleeptests"
-          android:targetSandboxVersion="2">
+     package="android.os.inattentivesleeptests"
+     android:targetSandboxVersion="2">
     <application>
-        <activity android:name=".KeepScreenOnActivity">
+        <activity android:name=".KeepScreenOnActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEFAULT"/>
@@ -28,4 +29,3 @@
         </activity>
     </application>
 </manifest>
-
diff --git a/hostsidetests/os/test-apps/PowerManagerTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/PowerManagerTestApp/AndroidManifest.xml
index 4fb0653..08e418f 100755
--- a/hostsidetests/os/test-apps/PowerManagerTestApp/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/PowerManagerTestApp/AndroidManifest.xml
@@ -16,11 +16,12 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.os.powermanagertests"
-    android:targetSandboxVersion="2">
-  <uses-permission android:name="android.permission.WAKE_LOCK" />
+     package="android.os.powermanagertests"
+     android:targetSandboxVersion="2">
+  <uses-permission android:name="android.permission.WAKE_LOCK"/>
   <application>
-    <activity android:name=".WakeLockTest">
+    <activity android:name=".WakeLockTest"
+         android:exported="true">
       <intent-filter>
         <action android:name="android.intent.action.MAIN"/>
         <category android:name="android.intent.category.DEFAULT"/>
@@ -29,4 +30,3 @@
     </activity>
   </application>
 </manifest>
-
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibTestApp/Android.bp
new file mode 100644
index 0000000..d715287
--- /dev/null
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsStaticSharedLibTestApp",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "cts-install-lib",
+    ],
+    java_resources: [
+        ":CtsStaticSharedLibProviderApp5",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
new file mode 100755
index 0000000..361d21a
--- /dev/null
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.os.lib.app">
+
+    <application>
+        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
+                  android:exported="true" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.os.lib.app"/>
+</manifest>
+
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/OWNERS b/hostsidetests/os/test-apps/StaticSharedLibTestApp/OWNERS
new file mode 100644
index 0000000..4247866
--- /dev/null
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 36137
+rhedjao@google.com
+patb@google.com
+toddke@google.com
+
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java b/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java
new file mode 100644
index 0000000..29bb524
--- /dev/null
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.lib.app;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Optional;
+
+/**
+ * On-device tests driven by StaticSharedLibsHostTests.
+ */
+@RunWith(AndroidJUnit4.class)
+public class StaticSharedLibsTests {
+
+    private static final String STATIC_LIB_PROVIDER5_PKG = "android.os.lib.provider";
+    private static final String STATIC_LIB_PROVIDER5_NAME = "android.os.lib.provider_2";
+    private static final TestApp TESTAPP_STATIC_LIB_PROVIDER5 = new TestApp(
+            "TestStaticSharedLibProvider5", STATIC_LIB_PROVIDER5_PKG, 1, /*isApex*/ false,
+            "CtsStaticSharedLibProviderApp5.apk");
+
+    @Before
+    public void setUp() throws Exception {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        Manifest.permission.INSTALL_PACKAGES);
+        clear();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        clear();
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testSamegradeStaticSharedLibFail() throws Exception {
+        Install.single(TESTAPP_STATIC_LIB_PROVIDER5).commit();
+        assertThat(getSharedLibraryInfo(STATIC_LIB_PROVIDER5_NAME)).isNotNull();
+
+        InstallUtils.commitExpectingFailure(AssertionError.class,
+                "Packages declaring static-shared libs cannot be updated",
+                Install.single(TESTAPP_STATIC_LIB_PROVIDER5));
+    }
+
+    private void clear() {
+        uninstallSharedLibrary(STATIC_LIB_PROVIDER5_PKG, STATIC_LIB_PROVIDER5_NAME);
+    }
+
+    private SharedLibraryInfo getSharedLibraryInfo(String libName) {
+        final PackageManager packageManager = InstrumentationRegistry.getContext()
+                .getPackageManager();
+        final Optional<SharedLibraryInfo> libraryInfo = packageManager.getSharedLibraries(0)
+                .stream().filter(lib -> lib.getName().equals(libName)).findAny();
+        return libraryInfo.isPresent() ? libraryInfo.get() : null;
+    }
+
+    private void uninstallSharedLibrary(String packageName, String libName) {
+        if (getSharedLibraryInfo(libName) == null) {
+            return;
+        }
+        runShellCommand("pm uninstall " + packageName);
+        assertThat(getSharedLibraryInfo(libName)).isNull();
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_helper.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_helper.xml
index 1373430..9b1e295 100644
--- a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_helper.xml
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_helper.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -17,10 +16,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.dynamicmime.helper">
+     package="android.dynamicmime.helper">
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.helper.FILTER_INFO_HOOK_group_first"/>
@@ -28,7 +28,8 @@
                 <data android:mimeGroup="group_first"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.helper.FILTER_INFO_HOOK_group_second"/>
@@ -38,15 +39,14 @@
         </activity>
 
         <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.helper" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.helper">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_preferred.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_preferred.xml
index a2b7c08..4dfa7d1 100644
--- a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_preferred.xml
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_preferred.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -17,11 +16,12 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.dynamicmime.preferred">
+     package="android.dynamicmime.preferred">
     <application android:testOnly="true"
-                 android:label="TestApp.Application">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+         android:label="TestApp.Application">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter android:label="TestApp.FirstActivity">
                 <action android:name="android.dynamicmime.preferred.TEST_ACTION"/>
                 <action android:name="android.dynamicmime.preferred.FILTER_INFO_HOOK_group_first"/>
@@ -30,7 +30,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.dynamicmime.common.activity.TwoGroupsActivity">
+        <activity android:name="android.dynamicmime.common.activity.TwoGroupsActivity"
+             android:exported="true">
             <intent-filter android:label="TestApp.TwoGroupsActivity">
                 <action android:name="android.dynamicmime.preferred.TEST_ACTION"/>
                 <action android:name="android.dynamicmime.preferred.FILTER_INFO_HOOK_group_both"/>
@@ -61,15 +62,14 @@
         </activity>
 
         <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.preferred">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.preferred">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_bothGroups.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_bothGroups.xml
index 802d13c..5241aa2 100644
--- a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_bothGroups.xml
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_bothGroups.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -17,10 +16,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.dynamicmime.update">
+     package="android.dynamicmime.update">
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_first"/>
@@ -28,7 +28,8 @@
                 <data android:mimeGroup="group_first"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_second"/>
@@ -38,15 +39,14 @@
         </activity>
 
         <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.update" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.update">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_firstGroup.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_firstGroup.xml
index adc93cf..f9ddf44 100644
--- a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_firstGroup.xml
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_firstGroup.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -17,10 +16,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.dynamicmime.update">
+     package="android.dynamicmime.update">
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_first"/>
@@ -28,7 +28,8 @@
                 <data android:mimeGroup="group_first"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_second"/>
@@ -37,15 +38,14 @@
         </activity>
 
         <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.update" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.update">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_secondGroup.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_secondGroup.xml
index e93a3d5..53e4273 100644
--- a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_secondGroup.xml
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_secondGroup.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -17,17 +16,19 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.dynamicmime.update">
+     package="android.dynamicmime.update">
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_first"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_second"/>
@@ -37,15 +38,14 @@
         </activity>
 
         <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.update" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.update">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/test/AndroidManifest.xml b/hostsidetests/packagemanager/dynamicmime/test/AndroidManifest.xml
index d31dcf3..102a800 100644
--- a/hostsidetests/packagemanager/dynamicmime/test/AndroidManifest.xml
+++ b/hostsidetests/packagemanager/dynamicmime/test/AndroidManifest.xml
@@ -16,11 +16,12 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.dynamicmime.testapp">
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+     package="android.dynamicmime.testapp">
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.testapp.FILTER_INFO_HOOK_group_first"/>
@@ -28,7 +29,8 @@
                 <data android:mimeGroup="group_first"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.testapp.FILTER_INFO_HOOK_group_second"/>
@@ -37,7 +39,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.dynamicmime.common.activity.TwoGroupsActivity">
+        <activity android:name="android.dynamicmime.common.activity.TwoGroupsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <category android:name="android.intent.category.DEFAULT"/>
@@ -47,7 +50,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.dynamicmime.common.activity.TwoGroupsAndTypeActivity">
+        <activity android:name="android.dynamicmime.common.activity.TwoGroupsAndTypeActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <category android:name="android.intent.category.DEFAULT"/>
@@ -59,8 +63,7 @@
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.testapp">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.testapp">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/extractnativelibs/Android.bp b/hostsidetests/packagemanager/extractnativelibs/Android.bp
index 634b40d..952d7bf 100644
--- a/hostsidetests/packagemanager/extractnativelibs/Android.bp
+++ b/hostsidetests/packagemanager/extractnativelibs/Android.bp
@@ -25,6 +25,7 @@
         "cts-tradefed",
         "tradefed",
         "compatibility-host-util",
+        "cts-host-utils",
     ],
     java_resource_dirs: ["res"],
 }
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp b/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp
index 5ae4dd3..85dc95f 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp
@@ -22,11 +22,52 @@
         "-Wno-unused-parameter",
     ],
     header_libs: ["jni_headers"],
+    shared_libs: ["liblog"],
     sdk_version: "current",
 }
 
 android_test_helper_app {
-    name: "CtsExtractNativeLibsAppFalse",
+    name: "CtsExtractNativeLibsAppFalse32",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    srcs: ["app_no_extract/src/**/*.java"],
+    manifest: "app_no_extract/AndroidManifest.xml",
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    jni_libs: [
+        "libtest_extract_native_libs",
+    ],
+    static_libs: ["androidx.test.rules"],
+    use_embedded_native_libs: true,
+    compile_multilib: "32",
+    v4_signature: true,
+}
+
+android_test_helper_app {
+    name: "CtsExtractNativeLibsAppFalse64",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    srcs: ["app_no_extract/src/**/*.java"],
+    manifest: "app_no_extract/AndroidManifest.xml",
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    jni_libs: [
+        "libtest_extract_native_libs",
+    ],
+    static_libs: ["androidx.test.rules"],
+    use_embedded_native_libs: true,
+    compile_multilib: "64",
+    v4_signature: true,
+}
+
+android_test_helper_app {
+    name: "CtsExtractNativeLibsAppFalseBoth",
     defaults: ["cts_defaults"],
     sdk_version: "current",
     srcs: ["app_no_extract/src/**/*.java"],
@@ -45,7 +86,47 @@
 }
 
 android_test_helper_app {
-    name: "CtsExtractNativeLibsAppTrue",
+    name: "CtsExtractNativeLibsAppTrue32",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    srcs: ["app_extract/src/**/*.java"],
+    manifest: "app_extract/AndroidManifest.xml",
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    jni_libs: [
+        "libtest_extract_native_libs",
+    ],
+    static_libs: ["androidx.test.rules"],
+    use_embedded_native_libs: false,
+    compile_multilib: "32",
+    v4_signature: true,
+}
+
+android_test_helper_app {
+    name: "CtsExtractNativeLibsAppTrue64",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    srcs: ["app_extract/src/**/*.java"],
+    manifest: "app_extract/AndroidManifest.xml",
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    jni_libs: [
+        "libtest_extract_native_libs",
+    ],
+    static_libs: ["androidx.test.rules"],
+    use_embedded_native_libs: false,
+    compile_multilib: "64",
+    v4_signature: true,
+}
+
+android_test_helper_app {
+    name: "CtsExtractNativeLibsAppTrueBoth",
     defaults: ["cts_defaults"],
     sdk_version: "current",
     srcs: ["app_extract/src/**/*.java"],
@@ -62,4 +143,3 @@
     compile_multilib: "both",
     v4_signature: true,
 }
-
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/AndroidManifest.xml b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/AndroidManifest.xml
index 27214b8..a82412a 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/AndroidManifest.xml
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/AndroidManifest.xml
@@ -15,13 +15,23 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.extractnativelibs.app.extract" >
-    <application android:extractNativeLibs="true" >
-        <uses-library android:name="android.test.runner" />
+          package="com.android.cts.extractnativelibs.app.extract">
+    <application android:extractNativeLibs="true">
+        <uses-library android:name="android.test.runner"/>
+        <!-- starting activity as a separate process, otherwise it'll always be 64-bit -->
+        <activity android:name=".MainActivity"
+                  android:exported="true"
+                  android:process=":NewProcess">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.extractnativelibs.app.extract" />
+        android:targetPackage="com.android.cts.extractnativelibs.app.extract"/>
 
 </manifest>
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/ExtractNativeLibsTrueDeviceTest.java b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/ExtractNativeLibsTrueDeviceTest.java
index 28367b2..4755b10 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/ExtractNativeLibsTrueDeviceTest.java
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/ExtractNativeLibsTrueDeviceTest.java
@@ -16,6 +16,11 @@
 
 package com.android.cts.extractnativelibs.app.extract;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -24,17 +29,49 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /** Device test for extractNativeLibs=true */
 @RunWith(AndroidJUnit4.class)
 public class ExtractNativeLibsTrueDeviceTest {
+    private final Context mContext =  InstrumentationRegistry.getContext();
 
     /** Test that the native lib dir exists and has an native lib file extracted in it. */
     @Test
-    public void testNativeLibsExtracted() throws Exception {
-        File nativeLibDir = new File(
-                InstrumentationRegistry.getContext().getApplicationInfo().nativeLibraryDir);
+    public void testNativeLibsExtracted() {
+        final String expectedSubDirArg = "expectedSubDir";
+        String expectedSubDir = InstrumentationRegistry.getArguments()
+                .getString(expectedSubDirArg);
+        Assert.assertNotNull(expectedSubDir);
+        File nativeLibDir = new File(mContext.getApplicationInfo().nativeLibraryDir);
+        Assert.assertTrue(nativeLibDir.exists());
         Assert.assertTrue(nativeLibDir.isDirectory());
+        Assert.assertTrue(nativeLibDir.getAbsolutePath().endsWith(expectedSubDir));
         Assert.assertEquals(1, nativeLibDir.list().length);
     }
+
+    /** Test that the native lib is loaded when the activity is launched. */
+    @Test
+    public void testNativeLibsLoaded() throws Exception {
+        final CountDownLatch loaded = new CountDownLatch(1);
+        IntentFilter filter = new IntentFilter(mContext.getPackageName() + ".NativeLibLoaded");
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                loaded.countDown();
+            }
+        };
+        mContext.registerReceiver(receiver, filter);
+        launchActivity();
+        Assert.assertTrue("Native lib not loaded", loaded.await(
+                30, TimeUnit.SECONDS));
+    }
+
+    private void launchActivity() {
+        Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(
+                mContext.getPackageName());
+        Assert.assertNotNull(launchIntent);
+        mContext.startActivity(launchIntent);
+    }
 }
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/MainActivity.java b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/MainActivity.java
new file mode 100644
index 0000000..fbcc783
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/MainActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.extractnativelibs.app.extract;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Launch activity for test app
+ */
+public class MainActivity extends Activity {
+    static {
+        System.loadLibrary("test_extract_native_libs");
+    }
+
+    @Override
+    public void onCreate(Bundle savedOnstanceState) {
+        // The native lib should have been loaded already
+        Intent intent = new Intent(
+                getApplicationContext().getPackageName() + ".NativeLibLoaded");
+        sendBroadcast(intent);
+    }
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/AndroidManifest.xml b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/AndroidManifest.xml
index ee1f8f5..d5d1e04 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/AndroidManifest.xml
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/AndroidManifest.xml
@@ -15,12 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.extractnativelibs.app.noextract" >
-    <application android:extractNativeLibs="false" >
-      <uses-library android:name="android.test.runner" />
+          package="com.android.cts.extractnativelibs.app.noextract">
+    <application android:extractNativeLibs="false">
+        <uses-library android:name="android.test.runner"/>
+        <!-- starting activity as a separate process, otherwise it'll always be 64-bit -->
+        <activity android:name=".MainActivity"
+                  android:exported="true"
+                  android:process=":NewProcess">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.extractnativelibs.app.noextract" />
+        android:targetPackage="com.android.cts.extractnativelibs.app.noextract"/>
 </manifest>
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/ExtractNativeLibsFalseDeviceTest.java b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/ExtractNativeLibsFalseDeviceTest.java
index a02c327..77a93b4 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/ExtractNativeLibsFalseDeviceTest.java
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/ExtractNativeLibsFalseDeviceTest.java
@@ -16,6 +16,11 @@
 
 package com.android.cts.extractnativelibs.app.noextract;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -24,17 +29,44 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /** Device test for extractNativeLibs=false */
 @RunWith(AndroidJUnit4.class)
 public class ExtractNativeLibsFalseDeviceTest {
+    private final Context mContext =  InstrumentationRegistry.getContext();
 
     /** Test that the native lib dir exists but has no native lib file in it. */
     @Test
-    public void testNativeLibsNotExtracted() throws Exception {
+    public void testNativeLibsNotExtracted() {
         File nativeLibDir = new File(
                 InstrumentationRegistry.getContext().getApplicationInfo().nativeLibraryDir);
         Assert.assertTrue(nativeLibDir.isDirectory());
         Assert.assertEquals(0, nativeLibDir.list().length);
     }
+
+    /** Test that the native lib is loaded when the activity is launched. */
+    @Test
+    public void testNativeLibsLoaded() throws Exception {
+        final CountDownLatch loaded = new CountDownLatch(1);
+        IntentFilter filter = new IntentFilter(mContext.getPackageName() + ".NativeLibLoaded");
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                loaded.countDown();
+            }
+        };
+        mContext.registerReceiver(receiver, filter);
+        launchActivity();
+        Assert.assertTrue("Native lib not loaded", loaded.await(
+                30, TimeUnit.SECONDS));
+    }
+
+    private void launchActivity() {
+        Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(
+                mContext.getPackageName());
+        Assert.assertNotNull(launchIntent);
+        mContext.startActivity(launchIntent);
+    }
 }
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/MainActivity.java b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/MainActivity.java
new file mode 100644
index 0000000..9200260
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/MainActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.extractnativelibs.app.noextract;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Launch activity for test app
+ */
+public class MainActivity extends Activity {
+    static {
+        System.loadLibrary("test_extract_native_libs");
+    }
+
+    @Override
+    public void onCreate(Bundle savedOnstanceState) {
+        // The native lib should have been loaded already
+        Intent intent = new Intent(
+                getApplicationContext().getPackageName() + ".NativeLibLoaded");
+        sendBroadcast(intent);
+    }
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/jni/native.cpp b/hostsidetests/packagemanager/extractnativelibs/apps/jni/native.cpp
index 52876f5..f925c5856 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/jni/native.cpp
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/jni/native.cpp
@@ -16,10 +16,14 @@
 
 #include <jni.h>
 
+#include <android/log.h>
+#define LOG(...) __android_log_write(ANDROID_LOG_INFO, "NativeLibOutput", __VA_ARGS__)
+
 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
     JNIEnv* env = nullptr;
     if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
         return JNI_ERR;
     }
+    LOG("libtest_extract_native_libs is loaded");
     return JNI_VERSION_1_6;
 }
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestAbiOverride.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestAbiOverride.java
new file mode 100644
index 0000000..3e33b43
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestAbiOverride.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.extractnativelibs.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.util.AbiUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Host test to update test apps with different ABIs.
+ */
+@RunWith(DeviceJUnit4Parameterized.class)
+@UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
+public class CtsExtractNativeLibsHostTestAbiOverride extends CtsExtractNativeLibsHostTestBase {
+    @Parameter(0)
+    public boolean mIsExtractNativeLibs;
+
+    @Parameter(1)
+    public boolean mIsIncremental;
+
+    @Parameter(2)
+    public String mFirstAbi;
+
+    @Parameter(3)
+    public String mSecondAbi;
+
+    @Override
+    public void setUp() throws Exception {
+        final String deviceAbi = getDeviceAbi();
+        // Only run these tests if device supports both 32-bit and 64-bit
+        assumeTrue(AbiUtils.getBitness(deviceAbi).equals("64"));
+        // Only run these tests for supported ABIs
+        assumeTrue(AbiUtils.getBaseArchForAbi(deviceAbi).equals(
+                AbiUtils.getBaseArchForAbi(mFirstAbi)));
+        if (mIsIncremental) {
+            // Skip incremental installations for non-incremental devices
+            assumeTrue(isIncrementalInstallSupported());
+        }
+        super.setUp();
+    }
+
+    /**
+     * Generate parameters for mutations of extract/embedded, incremental/legacy,
+     * and apps of an abi override updating to another abi override
+     */
+    @Parameterized.Parameters(name = "{index}: Test with mIsExtractNativeLibs={0}, "
+            + "mIsIncremental={1}, mFirstAbi={2}, mSecondAbi={3}")
+    public static Collection<Object[]> data() {
+        final boolean[] isExtractNativeLibsParams = new boolean[]{false, true};
+        final boolean[] isIncrementalParams = new boolean[]{false, true};
+        // We don't know the supported ABIs ahead of the time, here we enumerate all possible ones
+        // and filter unsupported ones during tests
+        final Set<String> supportedAbis = AbiUtils.getAbisSupportedByCompatibility();
+        ArrayList<Object[]> params = new ArrayList<>();
+        for (boolean isExtractNativeLibs : isExtractNativeLibsParams) {
+            for (boolean isIncremental : isIncrementalParams) {
+                for (String firstAbi : supportedAbis) {
+                    for (String secondAbi : supportedAbis) {
+                        if (!firstAbi.equals(secondAbi)
+                                && AbiUtils.getBaseArchForAbi(firstAbi).equals(
+                                        AbiUtils.getBaseArchForAbi(secondAbi))) {
+                            params.add(new Object[]{isExtractNativeLibs, isIncremental,
+                                    firstAbi, secondAbi});
+                        }
+                    }
+                    params.add(new Object[]{isExtractNativeLibs, isIncremental,
+                            firstAbi, "-"});
+                }
+            }
+        }
+        return params;
+    }
+
+    /**
+     * Test update installs with abi override and runs. Verify native lib dir layout.
+     */
+    @Test
+    @AppModeFull
+    public void testAbiOverrideAndRunSuccess() throws Exception {
+        final String testPackageName = getTestPackageName(mIsExtractNativeLibs);
+        final String testClassName = getTestClassName(mIsExtractNativeLibs);
+        // First install with one abi override
+        installPackage(mIsIncremental, getTestApkName(mIsExtractNativeLibs, "Both"), mFirstAbi);
+        assertTrue(isPackageInstalled(testPackageName));
+        assertTrue(runDeviceTests(testPackageName, testClassName, TEST_NATIVE_LIB_LOADED_TEST));
+        assertEquals(mFirstAbi, getPackageAbi(testPackageName));
+        assertTrue(checkNativeLibDir(mIsExtractNativeLibs, AbiUtils.getBitness(mFirstAbi)));
+        // Then update with another abi override
+        installPackage(mIsIncremental, getTestApkName(mIsExtractNativeLibs, "Both"), mSecondAbi);
+        assertTrue(runDeviceTests(testPackageName, testClassName, TEST_NATIVE_LIB_LOADED_TEST));
+        final String expectedAbi;
+        if (mSecondAbi.equals("-")) {
+            expectedAbi = getExpectedLibAbi("Both");
+        } else {
+            expectedAbi = mSecondAbi;
+        }
+        assertEquals(expectedAbi, getPackageAbi(testPackageName));
+        assertTrue(checkNativeLibDir(mIsExtractNativeLibs, AbiUtils.getBitness(expectedAbi)));
+    }
+
+    private String getPackageAbi(String testPackageName) throws Exception {
+        String commandResult = getDevice().executeShellCommand("pm dump " + testPackageName);
+        Optional<String> maybePrimaryCpuAbiStr = Arrays.stream(commandResult.split("\\r?\\n"))
+                .filter(line -> line.contains("primaryCpuAbi"))
+                .findFirst();
+        assertTrue(maybePrimaryCpuAbiStr.isPresent());
+        return maybePrimaryCpuAbiStr.get().substring(maybePrimaryCpuAbiStr.get().indexOf("=") + 1);
+    }
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestBase.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestBase.java
index 9d9aa3b1..cd47637 100644
--- a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestBase.java
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestBase.java
@@ -15,7 +15,16 @@
  */
 package android.extractnativelibs.cts;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.FileUtil;
 
 import org.junit.After;
@@ -26,6 +35,9 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * TODO(b/147496159): add more tests.
@@ -35,27 +47,27 @@
     static final String TEST_APK_RESOURCE_PREFIX = "/prebuilt/";
     static final String TEST_HOST_TMP_DIR_PREFIX = "cts_extract_native_libs_host_test";
 
-    static final String TEST_NO_EXTRACT_PKG =
-            "com.android.cts.extractnativelibs.app.noextract";
+    static final String TEST_APK_NAME_BASE = "CtsExtractNativeLibsApp";
+    static final String TEST_PKG_NAME_BASE = "com.android.cts.extractnativelibs.app";
+    static final String TEST_NO_EXTRACT_PKG = TEST_PKG_NAME_BASE + ".noextract";
     static final String TEST_NO_EXTRACT_CLASS =
             TEST_NO_EXTRACT_PKG + ".ExtractNativeLibsFalseDeviceTest";
     static final String TEST_NO_EXTRACT_TEST = "testNativeLibsNotExtracted";
-    static final String TEST_NO_EXTRACT_APK = "CtsExtractNativeLibsAppFalse.apk";
 
-    static final String TEST_EXTRACT_PKG =
-            "com.android.cts.extractnativelibs.app.extract";
+    static final String TEST_EXTRACT_PKG = TEST_PKG_NAME_BASE + ".extract";
     static final String TEST_EXTRACT_CLASS =
             TEST_EXTRACT_PKG + ".ExtractNativeLibsTrueDeviceTest";
     static final String TEST_EXTRACT_TEST = "testNativeLibsExtracted";
-    static final String TEST_EXTRACT_APK = "CtsExtractNativeLibsAppTrue.apk";
-    static final String TEST_NO_EXTRACT_MISALIGNED_APK =
-            "CtsExtractNativeLibsAppFalseWithMisalignedLib.apk";
+
+    static final String TEST_NATIVE_LIB_LOADED_TEST = "testNativeLibsLoaded";
+    static final String IDSIG_SUFFIX = ".idsig";
 
     /** Setup test dir. */
     @Before
     public void setUp() throws Exception {
         getDevice().executeShellCommand("mkdir " + TEST_REMOTE_DIR);
     }
+
     /** Uninstall apps after tests. */
     @After
     public void cleanUp() throws Exception {
@@ -64,8 +76,44 @@
         getDevice().executeShellCommand("rm -r " + TEST_REMOTE_DIR);
     }
 
-    File getFileFromResource(String filenameInResources)
-            throws Exception {
+    boolean isIncrementalInstallSupported() throws Exception {
+        return getDevice().hasFeature("android.software.incremental_delivery");
+    }
+
+    static String getTestApkName(boolean isExtractNativeLibs, String abiSuffix) {
+        return TEST_APK_NAME_BASE + (isExtractNativeLibs ? "True" : "False") + abiSuffix + ".apk";
+    }
+
+    static String getTestPackageName(boolean isExtractNativeLibs) {
+        return isExtractNativeLibs ? TEST_EXTRACT_PKG : TEST_NO_EXTRACT_PKG;
+    }
+
+    static String getTestClassName(boolean isExtractNativeLibs) {
+        return isExtractNativeLibs ? TEST_EXTRACT_CLASS : TEST_NO_EXTRACT_CLASS;
+    }
+
+    final void installPackage(boolean isIncremental, String apkName) throws Exception {
+        installPackage(isIncremental, apkName, "");
+    }
+
+    final void installPackage(boolean isIncremental, String apkName, String abi) throws Exception {
+        if (isIncremental) {
+            installPackageIncremental(apkName, abi);
+        } else {
+            installPackageLegacy(apkName, abi);
+        }
+    }
+
+    final boolean checkNativeLibDir(boolean isExtractNativeLibs, String abi) throws Exception {
+        if (isExtractNativeLibs) {
+            return checkExtractedNativeLibDirForAbi(abi);
+        } else {
+            return runDeviceTests(
+                    TEST_NO_EXTRACT_PKG, TEST_NO_EXTRACT_CLASS, TEST_NO_EXTRACT_TEST);
+        }
+    }
+
+    File getFileFromResource(String filenameInResources) throws Exception {
         String fullResourceName = TEST_APK_RESOURCE_PREFIX + filenameInResources;
         File tempDir = FileUtil.createTempDir(TEST_HOST_TMP_DIR_PREFIX);
         File file = new File(tempDir, filenameInResources);
@@ -83,4 +131,101 @@
         return file;
     }
 
+    private boolean runDeviceTestsWithArgs(String pkgName, String testClassName,
+            String testMethodName, Map<String, String> testArgs) throws Exception {
+        final String testRunner = "androidx.test.runner.AndroidJUnitRunner";
+        final long defaultTestTimeoutMs = 60 * 1000L;
+        final long defaultMaxTimeoutToOutputMs = 60 * 1000L; // 1min
+        return runDeviceTests(getDevice(), testRunner, pkgName, testClassName, testMethodName,
+                null, defaultTestTimeoutMs, defaultMaxTimeoutToOutputMs,
+                0L, true, false, testArgs);
+    }
+
+    private void installPackageLegacy(String apkFileName, String abi)
+            throws DeviceNotAvailableException, TargetSetupError {
+        SuiteApkInstaller installer = new SuiteApkInstaller();
+        installer.addTestFileName(apkFileName);
+        final String abiFlag = createAbiFlag(abi);
+        if (!abiFlag.isEmpty()) {
+            installer.addInstallArg(abiFlag);
+        }
+        try {
+            installer.setUp(getTestInformation());
+        } catch (BuildError e) {
+            throw new TargetSetupError(e.getMessage(), e, getDevice().getDeviceDescriptor());
+        }
+    }
+
+    private boolean checkExtractedNativeLibDirForAbi(String abiSuffix) throws Exception {
+        final String libAbi = getExpectedLibAbi(abiSuffix);
+        assertNotNull(libAbi);
+        final String expectedSubDirArg = "expectedSubDir";
+        final String expectedNativeLibSubDir = AbiUtils.getArchForAbi(libAbi);
+        final Map<String, String> testArgs = new HashMap<>();
+        testArgs.put(expectedSubDirArg, expectedNativeLibSubDir);
+        return runDeviceTestsWithArgs(TEST_EXTRACT_PKG, TEST_EXTRACT_CLASS, TEST_EXTRACT_TEST,
+                testArgs);
+    }
+
+    /** Given the abi included in the APK, predict which abi libs will be installed
+     * @param abiSuffix "64" means the APK contains only 64-bit native libs
+     *                  "32" means the APK contains only 32-bit native libs
+     *                  "Both" means the APK contains both 32-bit and 64-bit native libs
+     * @return an ABI string from AbiUtils.ABI_*
+     * @return an ABI string from AbiUtils.ABI_*
+     */
+    final String getExpectedLibAbi(String abiSuffix) throws Exception {
+        final String deviceAbi = getDeviceAbi();
+        final String deviceBitness = AbiUtils.getBitness(deviceAbi);
+        final String libBitness;
+        // Use 32-bit native libs if device only supports 32-bit or APK only has 32-libs native libs
+        if (abiSuffix.equals("32") || deviceBitness.equals("32")) {
+            libBitness = "32";
+        } else {
+            libBitness = "64";
+        }
+        final Set<String> libAbis = AbiUtils.getAbisForArch(AbiUtils.getBaseArchForAbi(deviceAbi));
+        for (String libAbi : libAbis) {
+            if (AbiUtils.getBitness(libAbi).equals(libBitness)) {
+                return libAbi;
+            }
+        }
+        return null;
+    }
+
+    final String getDeviceAbi() throws Exception {
+        return getDevice().getProperty("ro.product.cpu.abi");
+    }
+
+    private void installPackageIncremental(String apkName, String abi) throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File apk = buildHelper.getTestFile(apkName);
+        assertNotNull(apk);
+        final File v4Signature = buildHelper.getTestFile(apkName + IDSIG_SUFFIX);
+        assertNotNull(v4Signature);
+        installPackageIncrementalFromFiles(apk, v4Signature, abi);
+    }
+
+    private String installPackageIncrementalFromFiles(File apk, File v4Signature, String abi)
+            throws Exception {
+        final String remoteApkPath = TEST_REMOTE_DIR + "/" + apk.getName();
+        final String remoteIdsigPath = remoteApkPath + IDSIG_SUFFIX;
+        assertTrue(getDevice().pushFile(apk, remoteApkPath));
+        assertTrue(getDevice().pushFile(v4Signature, remoteIdsigPath));
+        return getDevice().executeShellCommand("pm install-incremental "
+                + createAbiFlag(abi)
+                + " -t -g " + remoteApkPath);
+    }
+
+    private String createAbiFlag(String abi) {
+        return abi.isEmpty() ? "" : ("--abi " + abi);
+    }
+
+    final String installIncrementalPackageFromResource(String apkFilenameInRes)
+            throws Exception {
+        final File apkFile = getFileFromResource(apkFilenameInRes);
+        final File v4SignatureFile = getFileFromResource(
+                apkFilenameInRes + IDSIG_SUFFIX);
+        return installPackageIncrementalFromFiles(apkFile, v4SignatureFile, "");
+    }
 }
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestFails.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestFails.java
new file mode 100644
index 0000000..f3d1946
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestFails.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.extractnativelibs.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test failure cases
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CtsExtractNativeLibsHostTestFails extends CtsExtractNativeLibsHostTestBase {
+    private static final String TEST_NO_EXTRACT_MISALIGNED_APK =
+            "CtsExtractNativeLibsAppFalseWithMisalignedLib.apk";
+
+    @Override
+    public void setUp() throws Exception {
+        // Skip incremental installations for non-incremental devices
+        assumeTrue(isIncrementalInstallSupported());
+        super.setUp();
+    }
+    /**
+     * Test with a app that has extractNativeLibs=false but with mis-aligned lib files,
+     * using Incremental install.
+     */
+    @Test
+    @AppModeFull
+    public void testExtractNativeLibsIncrementalFails() throws Exception {
+        String result = installIncrementalPackageFromResource(TEST_NO_EXTRACT_MISALIGNED_APK);
+        assertTrue(result.contains("Failed to extract native libraries"));
+        assertFalse(isPackageInstalled(TEST_NO_EXTRACT_PKG));
+    }
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestIncremental.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestIncremental.java
deleted file mode 100644
index 1fcf345..0000000
--- a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestIncremental.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.extractnativelibs.cts;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-/**
- * Host test to incremental install test apps and run device tests to verify the effect of
- * extractNativeLibs.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CtsExtractNativeLibsHostTestIncremental extends CtsExtractNativeLibsHostTestBase {
-    private static final String IDSIG_SUFFIX = ".idsig";
-
-    @Override
-    public void setUp() throws Exception {
-        assumeTrue(isIncrementalInstallSupported());
-        super.setUp();
-    }
-
-    private boolean isIncrementalInstallSupported() throws Exception {
-        return getDevice().hasFeature("android.software.incremental_delivery");
-    }
-    /** Test with a app that has extractNativeLibs=false using Incremental install. */
-    @Test
-    @AppModeFull
-    public void testNoExtractNativeLibsIncremental() throws Exception {
-        installPackageIncremental(TEST_NO_EXTRACT_APK);
-        assertTrue(isPackageInstalled(TEST_NO_EXTRACT_PKG));
-        assertTrue(runDeviceTests(
-                TEST_NO_EXTRACT_PKG, TEST_NO_EXTRACT_CLASS, TEST_NO_EXTRACT_TEST));
-    }
-
-    /** Test with a app that has extractNativeLibs=true using Incremental install. */
-    @Test
-    @AppModeFull
-    public void testExtractNativeLibsIncremental() throws Exception {
-        installPackageIncremental(TEST_EXTRACT_APK);
-        assertTrue(isPackageInstalled(TEST_EXTRACT_PKG));
-        assertTrue(runDeviceTests(
-                TEST_EXTRACT_PKG, TEST_EXTRACT_CLASS, TEST_EXTRACT_TEST));
-    }
-
-    /** Test with a app that has extractNativeLibs=false but with mis-aligned lib files,
-     *  using Incremental install. */
-    @Test
-    @AppModeFull
-    public void testExtractNativeLibsIncrementalFails() throws Exception {
-        String result = installIncrementalPackageFromResource(TEST_NO_EXTRACT_MISALIGNED_APK);
-        assertTrue(result.contains("Failed to extract native libraries"));
-        assertFalse(isPackageInstalled(TEST_NO_EXTRACT_PKG));
-    }
-
-    private void installPackageIncremental(String apkName) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
-        final File apk = buildHelper.getTestFile(apkName);
-        assertNotNull(apk);
-        final File v4Signature = buildHelper.getTestFile(apkName + IDSIG_SUFFIX);
-        assertNotNull(v4Signature);
-        installPackageIncrementalFromFiles(apk, v4Signature);
-    }
-
-    private String installPackageIncrementalFromFiles(File apk, File v4Signature) throws Exception {
-        final String remoteApkPath = TEST_REMOTE_DIR + "/" + apk.getName();
-        final String remoteIdsigPath = remoteApkPath + IDSIG_SUFFIX;
-        assertTrue(getDevice().pushFile(apk, remoteApkPath));
-        assertTrue(getDevice().pushFile(v4Signature, remoteIdsigPath));
-        return getDevice().executeShellCommand("pm install-incremental -t -g " + remoteApkPath);
-    }
-
-    private String installIncrementalPackageFromResource(String apkFilenameInRes)
-            throws Exception {
-        final File apkFile = getFileFromResource(apkFilenameInRes);
-        final File v4SignatureFile = getFileFromResource(
-                apkFilenameInRes + IDSIG_SUFFIX);
-        return installPackageIncrementalFromFiles(apkFile, v4SignatureFile);
-    }
-}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestInstalls.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestInstalls.java
new file mode 100644
index 0000000..53575c8
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestInstalls.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.extractnativelibs.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.util.AbiUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+/**
+ * Host test to install test apps and run device tests to verify the effect of extractNativeLibs.
+ */
+@RunWith(DeviceJUnit4Parameterized.class)
+@UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
+public class CtsExtractNativeLibsHostTestInstalls extends CtsExtractNativeLibsHostTestBase {
+    @Parameter(0)
+    public boolean mIsExtractNativeLibs;
+
+    @Parameter(1)
+    public boolean mIsIncremental;
+
+    @Parameter(2)
+    public String mAbiSuffix;
+
+    /**
+     * Generate parameters for mutations of extract/embedded, incremental/legacy and 32/64/Both ABIs
+     */
+    @Parameterized.Parameters(name = "{index}: Test with mIsExtractNativeLibs={0}, "
+            + "mIsIncremental={1}, mAbiSuffix={2}")
+    public static Collection<Object[]> data() {
+        final boolean[] isExtractNativeLibsParams = new boolean[]{false, true};
+        final boolean[] isIncrementalParams = new boolean[]{false, true};
+        final String[] abiSuffixParams = new String[]{"32", "64", "Both"};
+        ArrayList<Object[]> params = new ArrayList<>();
+        for (boolean isExtractNativeLibs : isExtractNativeLibsParams) {
+            for (boolean isIncremental : isIncrementalParams) {
+                for (String firstAbiSuffix : abiSuffixParams) {
+                    params.add(new Object[]{isExtractNativeLibs, isIncremental, firstAbiSuffix});
+                }
+            }
+        }
+        return params;
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        if (mIsIncremental) {
+            // Skip incremental installations for non-incremental devices
+            assumeTrue(isIncrementalInstallSupported());
+        }
+        if (mAbiSuffix.equals("64")) {
+            // Only run 64-bit tests if device supports both 32-bit and 64-bit
+            final String deviceAbi = getDeviceAbi();
+            assumeTrue(AbiUtils.getBitness(deviceAbi).equals("64"));
+        }
+        super.setUp();
+    }
+
+    /**
+     * Test app installs and runs. Verify native lib dir layout.
+     */
+    @Test
+    @AppModeFull
+    public void testInstallAndRunSuccess() throws Exception {
+        final String testApkName = getTestApkName(mIsExtractNativeLibs, mAbiSuffix);
+        final String testPackageName = getTestPackageName(mIsExtractNativeLibs);
+        final String testClassName = getTestClassName(mIsExtractNativeLibs);
+        installPackage(mIsIncremental, testApkName);
+        assertTrue(isPackageInstalled(testPackageName));
+        assertTrue(runDeviceTests(testPackageName, testClassName, TEST_NATIVE_LIB_LOADED_TEST));
+        assertTrue(checkNativeLibDir(mIsExtractNativeLibs, mAbiSuffix));
+    }
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestLegacy.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestLegacy.java
deleted file mode 100644
index ff58ee6..0000000
--- a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestLegacy.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.extractnativelibs.cts;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-/**
- * Host test to install test apps and run device tests to verify the effect of extractNativeLibs.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CtsExtractNativeLibsHostTestLegacy extends CtsExtractNativeLibsHostTestBase {
-    /** Test with a app that has extractNativeLibs=false. */
-    @Test
-    @AppModeFull
-    public void testNoExtractNativeLibsLegacy() throws Exception {
-        installPackage(TEST_NO_EXTRACT_APK);
-        assertTrue(isPackageInstalled(TEST_NO_EXTRACT_PKG));
-        assertTrue(runDeviceTests(
-                TEST_NO_EXTRACT_PKG, TEST_NO_EXTRACT_CLASS, TEST_NO_EXTRACT_TEST));
-    }
-
-    /** Test with a app that has extractNativeLibs=true. */
-    @Test
-    @AppModeFull
-    public void testExtractNativeLibsLegacy() throws Exception {
-        installPackage(TEST_EXTRACT_APK);
-        assertTrue(isPackageInstalled(TEST_EXTRACT_PKG));
-        assertTrue(runDeviceTests(
-                TEST_EXTRACT_PKG, TEST_EXTRACT_CLASS, TEST_EXTRACT_TEST));
-    }
-
-    /** Test with a app that has extractNativeLibs=false but with mis-aligned lib files */
-    @Test
-    @AppModeFull
-    public void testNoExtractNativeLibsFails() throws Exception {
-        File apk = getFileFromResource(TEST_NO_EXTRACT_MISALIGNED_APK);
-        String result = getDevice().installPackage(apk, false, true, "");
-        assertTrue(result.contains("Failed to extract native libraries"));
-        assertFalse(isPackageInstalled(TEST_NO_EXTRACT_PKG));
-    }
-
-}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestUpdates.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestUpdates.java
new file mode 100644
index 0000000..eec8fc2
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestUpdates.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.extractnativelibs.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.util.AbiUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+/**
+ * Host test to update test apps with different ABIs.
+ */
+@RunWith(DeviceJUnit4Parameterized.class)
+@UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
+public class CtsExtractNativeLibsHostTestUpdates extends CtsExtractNativeLibsHostTestBase {
+    @Parameter(0)
+    public boolean mIsExtractNativeLibs;
+
+    @Parameter(1)
+    public boolean mIsIncremental;
+
+    @Parameter(2)
+    public String mFirstAbiSuffix;
+
+    @Parameter(3)
+    public String mSecondAbiSuffix;
+
+    /**
+     * Generate parameters for mutations of extract/embedded, incremental/legacy,
+     * and apps of 32/64/Both ABIs updating to a ABI
+     */
+    @Parameterized.Parameters(name = "{index}: Test with mIsExtractNativeLibs={0}, "
+            + "mIsIncremental={1}, mFirstAbiSuffix={2}, mSecondAbiSuffix={3}")
+    public static Collection<Object[]> data() {
+        final boolean[] isExtractNativeLibsParams = new boolean[]{false, true};
+        final boolean[] isIncrementalParams = new boolean[]{false, true};
+        final String[] abiSuffixParams = new String[]{"32", "64", "Both"};
+        ArrayList<Object[]> params = new ArrayList<>();
+        for (boolean isExtractNativeLibs : isExtractNativeLibsParams) {
+            for (boolean isIncremental : isIncrementalParams) {
+                for (String firstAbiSuffix : abiSuffixParams) {
+                    for (String secondAbiSuffix : abiSuffixParams) {
+                        if (!firstAbiSuffix.equals(secondAbiSuffix)) {
+                            params.add(new Object[]{isExtractNativeLibs, isIncremental,
+                                    firstAbiSuffix, secondAbiSuffix});
+                        }
+                    }
+                }
+            }
+        }
+        return params;
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        if (mIsIncremental) {
+            // Skip incremental installations for non-incremental devices
+            assumeTrue(isIncrementalInstallSupported());
+        }
+        if (mFirstAbiSuffix.equals("64") || mSecondAbiSuffix.equals("64")) {
+            // Only run 64-bit tests if device supports both 32-bit and 64-bit
+            final String deviceAbi = getDeviceAbi();
+            assumeTrue(AbiUtils.getBitness(deviceAbi).equals("64"));
+        }
+        super.setUp();
+    }
+
+    /**
+     * Test update installs and runs. Verify native lib dir layout.
+     */
+    @Test
+    @AppModeFull
+    public void testUpdateAndRunSuccess() throws Exception {
+        final String testPackageName = getTestPackageName(mIsExtractNativeLibs);
+        final String testClassName = getTestClassName(mIsExtractNativeLibs);
+        installPackage(mIsIncremental, getTestApkName(mIsExtractNativeLibs, mFirstAbiSuffix));
+        assertTrue(isPackageInstalled(testPackageName));
+        installPackage(mIsIncremental, getTestApkName(mIsExtractNativeLibs, mSecondAbiSuffix));
+        assertTrue(runDeviceTests(testPackageName, testClassName, TEST_NATIVE_LIB_LOADED_TEST));
+        assertTrue(checkNativeLibDir(mIsExtractNativeLibs, mSecondAbiSuffix));
+    }
+}
diff --git a/hostsidetests/packagemanager/multiuser/Android.bp b/hostsidetests/packagemanager/multiuser/Android.bp
new file mode 100644
index 0000000..a6ae68a
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "CtsPackageManagerMultiUserHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+}
diff --git a/hostsidetests/packagemanager/multiuser/AndroidTest.xml b/hostsidetests/packagemanager/multiuser/AndroidTest.xml
new file mode 100644
index 0000000..0135fc4
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Config for CTS package manager multi-user host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsEmptyTestApp.apk" />
+        <option name="test-file-name" value="CtsPackageManagerMultiUserTestApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsPackageManagerMultiUserHostTestCases.jar" />
+    </test>
+</configuration>
+
diff --git a/hostsidetests/packagemanager/multiuser/app/Android.bp b/hostsidetests/packagemanager/multiuser/app/Android.bp
new file mode 100644
index 0000000..5e955e5
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/app/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+android_test_helper_app {
+    name: "CtsPackageManagerMultiUserTestApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    srcs: ["src/**/*.java",],
+    test_suites: [
+        "cts",
+        "vts",
+        "vts10",
+        "general-tests",
+    ],
+    static_libs: ["androidx.test.rules"],
+    compile_multilib: "both",
+}
diff --git a/hostsidetests/packagemanager/multiuser/app/AndroidManifest.xml b/hostsidetests/packagemanager/multiuser/app/AndroidManifest.xml
new file mode 100644
index 0000000..5fae893
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/app/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.tests.packagemanager.multiuser.app" >
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.tests.packagemanager.multiuser.app" />
+</manifest>
diff --git a/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java b/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java
new file mode 100644
index 0000000..80116e0
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.packagemanager.multiuser.app;
+
+import static org.junit.Assert.assertTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerMultiUserTest {
+    private static final String ARG_PACKAGE_NAME = "pkgName";
+
+    @After
+    public void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testUninstallExistingPackage() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.DELETE_PACKAGES);
+        String pkgName = InstrumentationRegistry.getArguments().getString(ARG_PACKAGE_NAME);
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        PackageManager packageManager = context.getPackageManager();
+        PackageInstaller packageInstaller = packageManager.getPackageInstaller();
+
+        packageInstaller.uninstallExistingPackage(pkgName, null);
+    }
+}
diff --git a/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTestBase.java b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTestBase.java
new file mode 100644
index 0000000..af82685
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTestBase.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.packagemanager.multiuser.host;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+public class PackageManagerMultiUserTestBase extends BaseHostJUnit4Test {
+    private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+    private static final String TEST_PACKAGE = "com.android.tests.packagemanager.multiuser.app";
+    private static final String TEST_CLASS = ".PackageManagerMultiUserTest";
+    private static final long DEFAULT_TIMEOUT = 10 * 60 * 1000L;
+
+    private List<Integer> mCreatedUsers;
+    protected int mUserId;
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue("Device does not support multiple users",
+                getDevice().getMaxNumberOfUsersSupported() > 1);
+        mUserId = getDevice().getPrimaryUserId();
+        mCreatedUsers = new ArrayList<>();
+    }
+
+    /** Remove created users after tests. */
+    @After
+    public void tearDown() throws Exception {
+        for (int userId : mCreatedUsers) {
+            removeUser(userId);
+        }
+    }
+
+    protected void runDeviceTestAsUser(
+            String pkgName, @Nullable String testClassName,
+            @Nullable String testMethodName, int userId,
+            Map<String, String> params) throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+
+        runDeviceTests(
+                getDevice(),
+                RUNNER,
+                pkgName,
+                testClassName,
+                testMethodName,
+                userId,
+                DEFAULT_TIMEOUT,
+                DEFAULT_TIMEOUT,
+                0L /* maxInstrumentationTimeoutMs */,
+                true /* checkResults */,
+                false /* isHiddenApiCheckDisabled */,
+                params == null ? Collections.emptyMap() : params);
+    }
+
+    protected void runDeviceTestAsUser(String testMethodName, int userId,
+            Map<String, String> params)
+            throws DeviceNotAvailableException {
+        runDeviceTestAsUser(TEST_PACKAGE, TEST_CLASS, testMethodName, userId, params);
+    }
+
+    protected int createUser() throws Exception {
+        String command = "pm create-user TestUser_" + System.currentTimeMillis();
+        LogUtil.CLog.d("Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        LogUtil.CLog.d("Output for command " + command + ": " + commandOutput);
+
+        // Extract the id of the new user.
+        String[] tokens = commandOutput.split("\\s+");
+        assertTrue(tokens.length > 0);
+        assertEquals("Success:", tokens[0]);
+        int userId = Integer.parseInt(tokens[tokens.length - 1]);
+        mCreatedUsers.add(userId);
+
+        setupUser(userId);
+        return userId;
+    }
+
+    protected void setupUser(int userId) throws Exception {
+        installExistingPackageForUser(TEST_PACKAGE, userId);
+    }
+
+    protected void removeUser(int userId) throws Exception {
+        String command = "pm remove-user " + userId;
+        LogUtil.CLog.d("Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        LogUtil.CLog.d("Output for command " + command + ": " + commandOutput);
+    }
+
+    protected void installExistingPackageForUser(String pkgName, int userId) throws Exception {
+        String command = "pm install-existing --user " + userId + " " + pkgName;
+        LogUtil.CLog.d("Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        LogUtil.CLog.d("Output for command " + command + ": " + commandOutput);
+    }
+
+    protected boolean isPackageInstalledForUser(String pkgName, int userId) throws Exception {
+        return getDevice().isPackageInstalled(pkgName, String.valueOf(userId));
+    }
+}
diff --git a/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/UninstallExistingPackageTest.java b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/UninstallExistingPackageTest.java
new file mode 100644
index 0000000..997b3ed
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/UninstallExistingPackageTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.packagemanager.multiuser.host;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class UninstallExistingPackageTest extends PackageManagerMultiUserTestBase {
+    private static final String EMPTY_TEST_APP_APK = "CtsEmptyTestApp.apk";
+    private static final String EMPTY_TEST_APP_PKG = "android.packageinstaller.emptytestapp.cts";
+    private static final String ARG_PACKAGE_NAME = "pkgName";
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        installPackage(EMPTY_TEST_APP_APK);
+        assertTrue(isPackageInstalled(EMPTY_TEST_APP_PKG));
+    }
+
+    /** Uninstall app after tests. */
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        uninstallPackage(getDevice(), EMPTY_TEST_APP_PKG);
+    }
+
+    @Test
+    @AppModeFull
+    public void testUninstallExistingPackage_succeedsIfInstalledInAnotherUser() throws Exception {
+        // create a  user
+        int newUserId = createUser();
+
+        // install empty test app for both users
+        installExistingPackageForUser(EMPTY_TEST_APP_PKG, newUserId);
+        assertTrue("Package is not installed for user " + mUserId,
+                isPackageInstalledForUser(EMPTY_TEST_APP_PKG, mUserId));
+        assertTrue("Package is not installed for user " + newUserId,
+                isPackageInstalledForUser(EMPTY_TEST_APP_PKG, newUserId));
+
+        // run uninstallExistingPackage from mUserId, expect package is uninstalled
+        runDeviceTestAsUser("testUninstallExistingPackage", mUserId,
+                Collections.singletonMap(ARG_PACKAGE_NAME, EMPTY_TEST_APP_PKG));
+        assertFalse(isPackageInstalledForUser(EMPTY_TEST_APP_PKG, mUserId));
+        assertTrue(isPackageInstalledForUser(EMPTY_TEST_APP_PKG, newUserId));
+    }
+
+    @Test
+    @AppModeFull
+    public void testUninstallExistingPackage_failsIfInstalledInOnlyOneUser() throws Exception {
+        // create a  user
+        int newUserId = createUser();
+
+        // assert package is only installed for mUserId
+        assertTrue(isPackageInstalledForUser(EMPTY_TEST_APP_PKG, mUserId));
+        assertFalse(isPackageInstalledForUser(EMPTY_TEST_APP_PKG, newUserId));
+
+        // run uninstallExistingPackage from mUserId, expect package is not uninstalled
+        runDeviceTestAsUser("testUninstallExistingPackage", mUserId,
+                Collections.singletonMap(ARG_PACKAGE_NAME, EMPTY_TEST_APP_PKG));
+        assertTrue(isPackageInstalledForUser(EMPTY_TEST_APP_PKG, mUserId));
+    }
+}
diff --git a/hostsidetests/rollback/Android.bp b/hostsidetests/rollback/Android.bp
index d58bc93..9871ab4 100644
--- a/hostsidetests/rollback/Android.bp
+++ b/hostsidetests/rollback/Android.bp
@@ -16,9 +16,8 @@
     name: "CtsRollbackManagerHostTestCases",
     defaults: ["cts_defaults"],
     srcs: ["src/**/*.java"],
-    libs: [
-        "cts-tradefed", "cts-shim-host-lib", "tradefed", "truth-prebuilt", "cts-install-lib-host"
-    ],
+    libs: ["cts-tradefed", "cts-shim-host-lib", "tradefed", "truth-prebuilt"],
+    static_libs: ["cts-install-lib-host"],
     data: [":CtsRollbackManagerHostTestHelperApp", ":CtsRollbackManagerHostTestHelperApp2"],
     test_suites: ["cts", "general-tests"],
 }
diff --git a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
index 129ffb6..d855a96 100644
--- a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
+++ b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
@@ -25,6 +25,7 @@
 
 import android.Manifest;
 import android.content.Context;
+import android.content.pm.PackageInstaller;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
 import android.os.storage.StorageManager;
@@ -651,10 +652,58 @@
     }
 
     @Test
-    public void isCheckpointSupported() {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
-        assertThat(sm.isCheckpointSupported()).isTrue();
+    public void testRollbackFailsBlockingSessions_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
+    }
+
+    @Test
+    public void testRollbackFailsBlockingSessions_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.A);
+        // Stage session for package A to check if it can block rollback of A
+        final int sessionIdA = Install.single(TestApp.A3).setStaged().setEnableRollback().commit();
+
+        // Stage another package not related to the rollback
+        Install.single(TestApp.B1).commit();
+        Install.single(TestApp.B2).setStaged().setEnableRollback().commit();
+
+        final RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+        final RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+        assertThat(committed).hasRollbackId(available.getRollbackId());
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+        // Assert that blocking staged session is failed
+        final PackageInstaller.SessionInfo sessionA = InstallUtils.getStagedSessionInfo(sessionIdA);
+        assertThat(sessionA).isNotNull();
+        assertThat(sessionA.isStagedSessionFailed()).isTrue();
+
+        // Note: The app is not rolled back until after the rollback is staged
+        // and the device has been rebooted.
+        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+    }
+
+    @Test
+    public void testRollbackFailsBlockingSessions_Phase3() throws Exception {
+        // Process TestApp.A
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+        final RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+        // Assert that unrelated package were not effected
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
     }
 
     /**
diff --git a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
index 0e2ddce..976e406 100644
--- a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
+++ b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
@@ -16,8 +16,6 @@
 
 package com.android.cts.rollback.host;
 
-import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.hamcrest.CoreMatchers.endsWith;
@@ -25,10 +23,8 @@
 import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.cts.install.lib.host.InstallUtilsHost;
-import com.android.ddmlib.Log;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
+import android.cts.install.lib.host.InstallUtilsHost;
+
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
@@ -69,48 +65,6 @@
     }
 
     /**
-     * Uninstalls a shim apex only if it's latest version is installed on /data partition (i.e.
-     * it has a version higher than {@code 1}).
-     *
-     * <p>This is purely to optimize tests run time, since uninstalling an apex requires a reboot.
-     */
-    private void uninstallShimApexIfNecessary() throws Exception {
-        if (!mHostUtils.isApexUpdateSupported()) {
-            // Device doesn't support updating apex. Nothing to uninstall.
-            return;
-        }
-
-        if (getShimApex().sourceDir.startsWith("/data")) {
-            final String errorMessage = getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
-            if (errorMessage == null) {
-                Log.i(TAG, "Uninstalling shim apex");
-                getDevice().reboot();
-            } else {
-                Log.e(TAG, "Failed to uninstall shim APEX : " + errorMessage);
-            }
-        }
-        assertThat(getShimApex().versionCode).isEqualTo(1L);
-    }
-
-    /**
-     * Get {@link ITestDevice.ApexInfo} for the installed shim apex.
-     */
-    private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException {
-        return getDevice().getActiveApexes().stream().filter(
-                apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME)).findAny().orElseThrow(
-                () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME));
-    }
-
-    private boolean isCheckpointSupported() throws Exception {
-        try {
-            run("isCheckpointSupported");
-            return true;
-        } catch (AssertionError ignore) {
-            return false;
-        }
-    }
-
-    /**
      * Uninstalls any version greater than 1 of shim apex and reboots the device if necessary
      * to complete the uninstall.
      *
@@ -126,7 +80,7 @@
         getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
         getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.B");
         run("cleanUp");
-        uninstallShimApexIfNecessary();
+        mHostUtils.uninstallShimApexIfNecessary();
     }
 
     /**
@@ -146,7 +100,9 @@
      */
     @Test
     public void testApkOnlyMultipleStagedRollback() throws Exception {
-        assumeTrue(isCheckpointSupported());
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
         run("testApkOnlyMultipleStagedRollback_Phase1");
         getDevice().reboot();
         run("testApkOnlyMultipleStagedRollback_Phase2");
@@ -159,7 +115,9 @@
      */
     @Test
     public void testApkOnlyMultipleStagedPartialRollback() throws Exception {
-        assumeTrue(isCheckpointSupported());
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
         run("testApkOnlyMultipleStagedPartialRollback_Phase1");
         getDevice().reboot();
         run("testApkOnlyMultipleStagedPartialRollback_Phase2");
@@ -251,6 +209,21 @@
     }
 
     /**
+     * Tests that existing staged sessions are failed when rollback is committed
+     */
+    @Test
+    public void testRollbackFailsBlockingSessions() throws Exception {
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
+        run("testRollbackFailsBlockingSessions_Phase1");
+        getDevice().reboot();
+        run("testRollbackFailsBlockingSessions_Phase2");
+        getDevice().reboot();
+        run("testRollbackFailsBlockingSessions_Phase3");
+    }
+
+    /**
      * Tests that rollbacks are invalidated upon fingerprint changes.
      */
     @Test
diff --git a/hostsidetests/sample/app/AndroidManifest.xml b/hostsidetests/sample/app/AndroidManifest.xml
index dfacf25..f1e8374 100755
--- a/hostsidetests/sample/app/AndroidManifest.xml
+++ b/hostsidetests/sample/app/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sample.app">
+     package="android.sample.app">
 
     <application>
-        <activity android:name=".SampleDeviceActivity" >
+        <activity android:name=".SampleDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index 1a1a7f9..57e0005 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -72,6 +72,16 @@
 }
 
 java_test_host {
+    name: "CtsScopedStorageCoreHostTest",
+    srcs:  [
+        "host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java"
+    ],
+    libs: ["tradefed", "testng"],
+    test_suites: ["general-tests", "mts", "cts"],
+    test_config: "CoreTest.xml",
+}
+
+java_test_host {
     name: "CtsScopedStorageHostTest",
     srcs: ["host/src/**/*.java"],
     libs: ["tradefed", "testng"],
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml
index 43208ac..bbdf653 100644
--- a/hostsidetests/scopedstorage/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <!-- TODO(b/169101565): change to secondary_user when fixed -->
     <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/scopedstorage/CoreTest.xml b/hostsidetests/scopedstorage/CoreTest.xml
new file mode 100644
index 0000000..c251d10
--- /dev/null
+++ b/hostsidetests/scopedstorage/CoreTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="External storage host test for legacy and scoped storage">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="ScopedStorageTest.apk" />
+        <option name="test-file-name" value="LegacyStorageTest.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="android.scopedstorage.cts.host.ScopedStorageCoreHostTest" />
+    </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+    </object>
+</configuration>
diff --git a/hostsidetests/scopedstorage/PublicVolumeTest.xml b/hostsidetests/scopedstorage/PublicVolumeTest.xml
index 8bd3361..e70217f 100644
--- a/hostsidetests/scopedstorage/PublicVolumeTest.xml
+++ b/hostsidetests/scopedstorage/PublicVolumeTest.xml
@@ -22,6 +22,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.scopedstorage.cts.host.PublicVolumeHostTest" />
+        <option name="class" value="android.scopedstorage.cts.host.PublicVolumeCoreHostTest" />
         <option name="class" value="android.scopedstorage.cts.host.PublicVolumeLegacyHostTest" />
     </test>
 
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
index 1747eb6..0ecc2a7 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
@@ -23,7 +23,7 @@
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
 
     <application android:label="TestAppA">
-        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper">
+        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
index cf9a327..076d299 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
@@ -23,7 +23,7 @@
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
 
     <application android:label="TestAppB">
-        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper">
+        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppC.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppC.xml
index e6ee00a..32b7a0e 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppC.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppC.xml
@@ -25,7 +25,7 @@
   <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
 
     <application android:label="TestAppC">
-        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper">
+        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppCLegacy.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppCLegacy.xml
index be1bd75..de1c54a 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppCLegacy.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppCLegacy.xml
@@ -23,7 +23,7 @@
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <application android:label="TestAppCLegacy">
-        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper">
+        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/hostsidetests/scopedstorage/TEST_MAPPING b/hostsidetests/scopedstorage/TEST_MAPPING
index 3f87702..28ecb56 100644
--- a/hostsidetests/scopedstorage/TEST_MAPPING
+++ b/hostsidetests/scopedstorage/TEST_MAPPING
@@ -1,6 +1,9 @@
 {
   "presubmit": [
     {
+      "name": "CtsScopedStorageCoreHostTest"
+    },
+    {
       "name": "CtsScopedStorageHostTest"
     },
     {
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/BaseHostTestCase.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/BaseHostTestCase.java
new file mode 100644
index 0000000..983cc66
--- /dev/null
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/BaseHostTestCase.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts.host;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.NativeDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+abstract class BaseHostTestCase extends BaseHostJUnit4Test {
+    private int mCurrentUserId = NativeDevice.INVALID_USER_ID;
+
+    protected String executeShellCommand(String cmd, Object... args) throws Exception {
+        return getDevice().executeShellCommand(String.format(cmd, args));
+    }
+
+    protected int getCurrentUserId() throws Exception {
+        setCurrentUserId();
+
+        return mCurrentUserId;
+    }
+
+    private void setCurrentUserId() throws Exception {
+        if (mCurrentUserId != NativeDevice.INVALID_USER_ID) return;
+
+        ITestDevice device = getDevice();
+        mCurrentUserId = device.getCurrentUser();
+        CLog.i("Current user: %d");
+    }
+}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
index 8729f9b..56f1412 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
@@ -18,12 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertTrue;
-
 import android.platform.test.annotations.AppModeFull;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.After;
 import org.junit.Before;
@@ -35,20 +32,17 @@
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
 @AppModeFull
-public class LegacyStorageHostTest extends BaseHostJUnit4Test {
-    private boolean isExternalStorageSetup = false;
+public class LegacyStorageHostTest extends BaseHostTestCase {
 
-    private String executeShellCommand(String cmd) throws Exception {
-        return getDevice().executeShellCommand(cmd);
-    }
+    private boolean mIsExternalStorageSetup;
 
     /**
      * Runs the given phase of LegacyFileAccessTest by calling into the device.
      * Throws an exception if the test phase fails.
      */
     void runDeviceTest(String phase) throws Exception {
-        assertTrue(runDeviceTests("android.scopedstorage.cts.legacy",
-                "android.scopedstorage.cts.legacy.LegacyStorageTest", phase));
+        assertThat(runDeviceTests("android.scopedstorage.cts.legacy",
+                "android.scopedstorage.cts.legacy.LegacyStorageTest", phase)).isTrue();
     }
 
     /**
@@ -56,14 +50,18 @@
      * so in order to test a case where the reader has only WRITE, we must explicitly revoke READ.
      */
     private void grantPermissions(String... perms) throws Exception {
+        int currentUserId = getCurrentUserId();
         for (String perm : perms) {
-            executeShellCommand("pm grant android.scopedstorage.cts.legacy " + perm);
+            executeShellCommand("pm grant --user %d android.scopedstorage.cts.legacy %s",
+                    currentUserId, perm);
         }
     }
 
     private void revokePermissions(String... perms) throws Exception {
+        int currentUserId = getCurrentUserId();
         for (String perm : perms) {
-            executeShellCommand("pm revoke android.scopedstorage.cts.legacy " + perm);
+            executeShellCommand("pm revoke --user %d android.scopedstorage.cts.legacy %s",
+                    currentUserId, perm);
         }
     }
 
@@ -72,14 +70,14 @@
      * creating file.
      */
     private void createFileAsShell(String filePath) throws Exception {
-        executeShellCommand("touch " + filePath);
+        executeShellCommand("touch %s", filePath);
         assertThat(getDevice().doesFileExist(filePath)).isTrue();
     }
 
     private void setupExternalStorage() throws Exception {
-        if (!isExternalStorageSetup) {
+        if (!mIsExternalStorageSetup) {
             runDeviceTest("setupExternalStorage");
-            isExternalStorageSetup = true;
+            mIsExternalStorageSetup = true;
         }
     }
 
@@ -199,4 +197,20 @@
     public void testInsertWithUnsupportedMimeType() throws Exception {
         runDeviceTest("testInsertWithUnsupportedMimeType");
     }
+
+    @Test
+    public void testLegacySystemGalleryCanRenameImagesAndVideosWithoutDbUpdates() throws Exception {
+        runDeviceTest("testLegacySystemGalleryCanRenameImagesAndVideosWithoutDbUpdates");
+    }
+
+    @Test
+    public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
+        revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+        runDeviceTest("testLegacySystemGalleryWithoutWESCannotRename");
+    }
+
+    @Test
+    public void testLegacyWESCanRenameImagesAndVideosWithDbUpdates_hasW() throws Exception {
+        runDeviceTest("testLegacyWESCanRenameImagesAndVideosWithDbUpdates_hasW");
+    }
 }
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeCoreHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeCoreHostTest.java
new file mode 100644
index 0000000..e92217d
--- /dev/null
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeCoreHostTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts.host;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.ITestDevice;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+
+public class PublicVolumeCoreHostTest extends ScopedStorageCoreHostTest {
+    /* Used to clean up the virtual volume after the test */
+    private static ITestDevice sDevice = null;
+    private boolean mIsPublicVolumeSetup = false;
+    String executeShellCommand(String cmd) throws Exception {
+        return getDevice().executeShellCommand(cmd);
+    }
+
+    private void setupNewPublicVolume() throws Exception {
+        if (!mIsPublicVolumeSetup) {
+            assertTrue(runDeviceTests("android.scopedstorage.cts",
+                    "android.scopedstorage.cts.PublicVolumeTestHelper", "setupNewPublicVolume"));
+            mIsPublicVolumeSetup = true;
+        }
+    }
+
+    private void setupDevice() {
+        if (sDevice == null) {
+            sDevice = getDevice();
+        }
+    }
+
+    /**
+     * Runs the given phase of PublicVolumeTest by calling into the device.
+     * Throws an exception if the test phase fails.
+     */
+    @Override
+    void runDeviceTest(String phase) throws Exception {
+        assertTrue(runDeviceTests("android.scopedstorage.cts",
+                "android.scopedstorage.cts.PublicVolumeTest", phase));
+    }
+
+    @Before
+    public void setup() throws Exception {
+        setupDevice();
+        setupNewPublicVolume();
+        super.setup();
+    }
+
+    @AfterClass
+    public static void deletePublicVolumes() throws Exception {
+        if (sDevice != null) {
+            sDevice.executeShellCommand("sm set-virtual-disk false");
+        }
+    }
+}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeHostTest.java
index dbfa9fb..256540a 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeHostTest.java
@@ -16,7 +16,7 @@
 
 package android.scopedstorage.cts.host;
 
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import com.android.tradefed.device.ITestDevice;
 
@@ -25,16 +25,14 @@
 
 public class PublicVolumeHostTest extends ScopedStorageHostTest {
     /** Used to clean up the virtual volume after the test */
-    private static ITestDevice sDevice = null;
-    private boolean mIsPublicVolumeSetup = false;
-    String executeShellCommand(String cmd) throws Exception {
-        return getDevice().executeShellCommand(cmd);
-    }
+    private static ITestDevice sDevice;
+    private boolean mIsPublicVolumeSetup;
 
     private void setupNewPublicVolume() throws Exception {
         if (!mIsPublicVolumeSetup) {
-            assertTrue(runDeviceTests("android.scopedstorage.cts",
-                    "android.scopedstorage.cts.PublicVolumeTestHelper", "setupNewPublicVolume"));
+            assertThat(runDeviceTests("android.scopedstorage.cts",
+                    "android.scopedstorage.cts.PublicVolumeTestHelper", "setupNewPublicVolume"))
+                            .isTrue();
             mIsPublicVolumeSetup = true;
         }
     }
@@ -51,8 +49,8 @@
      */
     @Override
     void runDeviceTest(String phase) throws Exception {
-        assertTrue(runDeviceTests("android.scopedstorage.cts",
-                "android.scopedstorage.cts.PublicVolumeTest", phase));
+        assertThat(runDeviceTests("android.scopedstorage.cts",
+                "android.scopedstorage.cts.PublicVolumeTest", phase)).isTrue();
     }
 
     @Before
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeLegacyHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeLegacyHostTest.java
index c9bd65f..4b38df1 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeLegacyHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeLegacyHostTest.java
@@ -16,7 +16,7 @@
 
 package android.scopedstorage.cts.host;
 
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -33,8 +33,9 @@
 
     private void setupNewPublicVolume() throws Exception {
         if (!mIsPublicVolumeSetup) {
-            assertTrue(runDeviceTests("android.scopedstorage.cts",
-                    "android.scopedstorage.cts.PublicVolumeTestHelper", "setupNewPublicVolume"));
+            assertThat(runDeviceTests("android.scopedstorage.cts",
+                    "android.scopedstorage.cts.PublicVolumeTestHelper", "setupNewPublicVolume"))
+                            .isTrue();
             mIsPublicVolumeSetup = true;
         }
     }
@@ -51,8 +52,8 @@
      */
     @Override
     void runDeviceTest(String phase) throws Exception {
-        assertTrue(runDeviceTests("android.scopedstorage.cts.legacy",
-                "android.scopedstorage.cts.legacy.PublicVolumeLegacyTest", phase));
+        assertThat(runDeviceTests("android.scopedstorage.cts.legacy",
+                "android.scopedstorage.cts.legacy.PublicVolumeLegacyTest", phase)).isTrue();
     }
 
     @Before
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java
new file mode 100644
index 0000000..e693f2a
--- /dev/null
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts.host;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs the core ScopedStorageTest tests.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class ScopedStorageCoreHostTest extends BaseHostJUnit4Test {
+    private boolean mIsExternalStorageSetup = false;
+
+    /**
+     * Runs the given phase of ScopedStorageTest by calling into the device.
+     * Throws an exception if the test phase fails.
+     */
+    void runDeviceTest(String phase) throws Exception {
+        assertTrue(runDeviceTests("android.scopedstorage.cts",
+                "android.scopedstorage.cts.ScopedStorageTest", phase));
+
+    }
+
+    String executeShellCommand(String cmd) throws Exception {
+        return getDevice().executeShellCommand(cmd);
+    }
+
+    private void setupExternalStorage() throws Exception {
+        if (!mIsExternalStorageSetup) {
+            runDeviceTest("setupExternalStorage");
+            mIsExternalStorageSetup = true;
+        }
+    }
+
+    @Before
+    public void setup() throws Exception {
+        setupExternalStorage();
+        executeShellCommand("mkdir /sdcard/Android/data/com.android.shell -m 2770");
+        executeShellCommand("mkdir /sdcard/Android/data/com.android.shell/files -m 2770");
+    }
+
+    @Before
+    public void revokeStoragePermissions() throws Exception {
+        revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE",
+                "android.permission.READ_EXTERNAL_STORAGE");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        executeShellCommand("rm -r /sdcard/Android/data/com.android.shell");
+    }
+
+    @Test
+    public void testTypePathConformity() throws Exception {
+        runDeviceTest("testTypePathConformity");
+    }
+
+    @Test
+    public void testCreateFileInAppExternalDir() throws Exception {
+        runDeviceTest("testCreateFileInAppExternalDir");
+    }
+
+    @Test
+    public void testCreateFileInOtherAppExternalDir() throws Exception {
+        runDeviceTest("testCreateFileInOtherAppExternalDir");
+    }
+
+    @Test
+    public void testContributeMediaFile() throws Exception {
+        runDeviceTest("testContributeMediaFile");
+    }
+
+    @Test
+    public void testCreateAndDeleteEmptyDir() throws Exception {
+        runDeviceTest("testCreateAndDeleteEmptyDir");
+    }
+
+    @Test
+    public void testOpendirRestrictions() throws Exception {
+        runDeviceTest("testOpendirRestrictions");
+    }
+
+    @Test
+    public void testLowLevelFileIO() throws Exception {
+        runDeviceTest("testLowLevelFileIO");
+    }
+
+    @Test
+    public void testListDirectoriesWithMediaFiles() throws Exception {
+        runDeviceTest("testListDirectoriesWithMediaFiles");
+    }
+
+    @Test
+    public void testListFilesFromExternalMediaDirectory() throws Exception {
+        runDeviceTest("testListFilesFromExternalMediaDirectory");
+    }
+
+    @Test
+    public void testMetaDataRedaction() throws Exception {
+        runDeviceTest("testMetaDataRedaction");
+    }
+
+    @Test
+    public void testVfsCacheConsistency() throws Exception {
+        runDeviceTest("testOpenFilePathFirstWriteContentResolver");
+        runDeviceTest("testOpenContentResolverFirstWriteContentResolver");
+        runDeviceTest("testOpenFilePathFirstWriteFilePath");
+        runDeviceTest("testOpenContentResolverFirstWriteFilePath");
+        runDeviceTest("testOpenContentResolverWriteOnly");
+        runDeviceTest("testOpenContentResolverDup");
+        runDeviceTest("testContentResolverDelete");
+        runDeviceTest("testContentResolverUpdate");
+        runDeviceTest("testOpenContentResolverClose");
+    }
+
+    @Test
+    public void testCaseInsensitivity() throws Exception {
+        runDeviceTest("testCreateLowerCaseDeleteUpperCase");
+        runDeviceTest("testCreateUpperCaseDeleteLowerCase");
+        runDeviceTest("testCreateMixedCaseDeleteDifferentMixedCase");
+        runDeviceTest("testAndroidDataObbDoesNotForgetMount");
+        runDeviceTest("testCacheConsistencyForCaseInsensitivity");
+    }
+
+    @Test
+    public void testRenameAndReplaceFile() throws Exception {
+        runDeviceTest("testRenameAndReplaceFile");
+    }
+
+    @Test
+    public void testRenameDirectory() throws Exception {
+        runDeviceTest("testRenameDirectory");
+    }
+
+    @Test
+    public void testSystemGalleryAppHasFullAccessToImages() throws Exception {
+        runDeviceTest("testSystemGalleryAppHasFullAccessToImages");
+    }
+
+    @Test
+    public void testSystemGalleryAppHasNoFullAccessToAudio() throws Exception {
+        runDeviceTest("testSystemGalleryAppHasNoFullAccessToAudio");
+    }
+
+    @Test
+    public void testSystemGalleryCanRenameImageAndVideoDirs() throws Exception {
+        runDeviceTest("testSystemGalleryCanRenameImageAndVideoDirs");
+    }
+
+    @Test
+    public void testManageExternalStorageCanCreateFilesAnywhere() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCanCreateFilesAnywhere");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testManageExternalStorageReaddir() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageReaddir");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testHiddenFiles() throws Exception {
+        runDeviceTest("testCanCreateHiddenFile");
+        runDeviceTest("testCanRenameHiddenFile");
+        runDeviceTest("testHiddenDirectory");
+    }
+
+    @Test
+    public void testCreateCanRestoreDeletedRowId() throws Exception {
+        runDeviceTest("testCreateCanRestoreDeletedRowId");
+    }
+
+    @Test
+    public void testRenameCanRestoreDeletedRowId() throws Exception {
+        runDeviceTest("testRenameCanRestoreDeletedRowId");
+    }
+
+    @Test
+    public void testQueryOtherAppsFiles() throws Exception {
+        runDeviceTest("testQueryOtherAppsFiles");
+    }
+
+    @Test
+    public void testAccess_file() throws Exception {
+        grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
+        try {
+            runDeviceTest("testAccess_file");
+        } finally {
+            revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
+        }
+    }
+
+    @Test
+    public void testAccess_directory() throws Exception {
+        grantPermissions("android.permission.READ_EXTERNAL_STORAGE",
+                "android.permission.WRITE_EXTERNAL_STORAGE");
+        try {
+            runDeviceTest("testAccess_directory");
+        } finally {
+            revokePermissions("android.permission.READ_EXTERNAL_STORAGE",
+                    "android.permission.WRITE_EXTERNAL_STORAGE");
+        }
+    }
+
+    private void grantPermissions(String... perms) throws Exception {
+        for (String perm : perms) {
+            executeShellCommand("pm grant android.scopedstorage.cts " + perm);
+        }
+    }
+
+    private void revokePermissions(String... perms) throws Exception {
+        for (String perm : perms) {
+            executeShellCommand("pm revoke android.scopedstorage.cts " + perm);
+        }
+    }
+
+    private void allowAppOps(String... ops) throws Exception {
+        for (String op : ops) {
+            executeShellCommand("cmd appops set --uid android.scopedstorage.cts " + op + " allow");
+        }
+    }
+
+    private void denyAppOps(String... ops) throws Exception {
+        for (String op : ops) {
+            executeShellCommand("cmd appops set --uid android.scopedstorage.cts " + op + " deny");
+        }
+    }
+}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index b20342c..c2b156b 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -16,13 +16,12 @@
 
 package android.scopedstorage.cts.host;
 
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.platform.test.annotations.AppModeFull;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
 
 import org.junit.After;
@@ -35,17 +34,16 @@
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
 @AppModeFull
-public class ScopedStorageHostTest extends BaseHostJUnit4Test {
-    private boolean mIsExternalStorageSetup = false;
+public class ScopedStorageHostTest extends BaseHostTestCase {
+    private boolean mIsExternalStorageSetup;
 
     /**
      * Runs the given phase of ScopedStorageTest by calling into the device.
      * Throws an exception if the test phase fails.
      */
     void runDeviceTest(String phase) throws Exception {
-        assertTrue(runDeviceTests("android.scopedstorage.cts",
-                "android.scopedstorage.cts.ScopedStorageTest", phase));
-
+        assertThat(runDeviceTests("android.scopedstorage.cts",
+                "android.scopedstorage.cts.ScopedStorageTest", phase)).isTrue();
     }
 
     /**
@@ -61,10 +59,6 @@
             .setDisableIsolatedStorage(true));
     }
 
-    String executeShellCommand(String cmd) throws Exception {
-        return getDevice().executeShellCommand(cmd);
-    }
-
     private void setupExternalStorage() throws Exception {
         if (!mIsExternalStorageSetup) {
             runDeviceTest("setupExternalStorage");
@@ -91,36 +85,11 @@
     }
 
     @Test
-    public void testTypePathConformity() throws Exception {
-        runDeviceTest("testTypePathConformity");
-    }
-
-    @Test
-    public void testCreateFileInAppExternalDir() throws Exception {
-        runDeviceTest("testCreateFileInAppExternalDir");
-    }
-
-    @Test
-    public void testCreateFileInOtherAppExternalDir() throws Exception {
-        runDeviceTest("testCreateFileInOtherAppExternalDir");
-    }
-
-    @Test
     public void testReadWriteFilesInOtherAppExternalDir() throws Exception {
         runDeviceTest("testReadWriteFilesInOtherAppExternalDir");
     }
 
     @Test
-    public void testContributeMediaFile() throws Exception {
-        runDeviceTest("testContributeMediaFile");
-    }
-
-    @Test
-    public void testCreateAndDeleteEmptyDir() throws Exception {
-        runDeviceTest("testCreateAndDeleteEmptyDir");
-    }
-
-    @Test
     public void testCantDeleteOtherAppsContents() throws Exception {
         runDeviceTest("testCantDeleteOtherAppsContents");
     }
@@ -130,20 +99,6 @@
         runDeviceTest("testDeleteAlreadyUnlinkedFile");
 
     }
-    @Test
-    public void testOpendirRestrictions() throws Exception {
-        runDeviceTest("testOpendirRestrictions");
-    }
-
-    @Test
-    public void testLowLevelFileIO() throws Exception {
-        runDeviceTest("testLowLevelFileIO");
-    }
-
-    @Test
-    public void testListDirectoriesWithMediaFiles() throws Exception {
-        runDeviceTest("testListDirectoriesWithMediaFiles");
-    }
 
     @Test
     public void testListDirectoriesWithNonMediaFiles() throws Exception {
@@ -156,43 +111,11 @@
     }
 
     @Test
-    public void testListFilesFromExternalMediaDirectory() throws Exception {
-        runDeviceTest("testListFilesFromExternalMediaDirectory");
-    }
-
-    @Test
     public void testListUnsupportedFileType() throws Exception {
         runDeviceTest("testListUnsupportedFileType");
     }
 
     @Test
-    public void testMetaDataRedaction() throws Exception {
-        runDeviceTest("testMetaDataRedaction");
-    }
-
-    @Test
-    public void testVfsCacheConsistency() throws Exception {
-        runDeviceTest("testOpenFilePathFirstWriteContentResolver");
-        runDeviceTest("testOpenContentResolverFirstWriteContentResolver");
-        runDeviceTest("testOpenFilePathFirstWriteFilePath");
-        runDeviceTest("testOpenContentResolverFirstWriteFilePath");
-        runDeviceTest("testOpenContentResolverWriteOnly");
-        runDeviceTest("testOpenContentResolverDup");
-        runDeviceTest("testContentResolverDelete");
-        runDeviceTest("testContentResolverUpdate");
-        runDeviceTest("testOpenContentResolverClose");
-    }
-
-    @Test
-    public void testCaseInsensitivity() throws Exception {
-        runDeviceTest("testCreateLowerCaseDeleteUpperCase");
-        runDeviceTest("testCreateUpperCaseDeleteLowerCase");
-        runDeviceTest("testCreateMixedCaseDeleteDifferentMixedCase");
-        runDeviceTest("testAndroidDataObbDoesNotForgetMount");
-        runDeviceTest("testCacheConsistencyForCaseInsensitivity");
-    }
-
-    @Test
     public void testCallingIdentityCacheInvalidation() throws Exception {
         // General IO access
         runDeviceTest("testReadStorageInvalidation");
@@ -220,21 +143,11 @@
     }
 
     @Test
-    public void testRenameAndReplaceFile() throws Exception {
-        runDeviceTest("testRenameAndReplaceFile");
-    }
-
-    @Test
     public void testRenameFileNotOwned() throws Exception {
         runDeviceTest("testRenameFileNotOwned");
     }
 
     @Test
-    public void testRenameDirectory() throws Exception {
-        runDeviceTest("testRenameDirectory");
-    }
-
-    @Test
     public void testRenameDirectoryNotOwned() throws Exception {
         runDeviceTest("testRenameDirectoryNotOwned");
     }
@@ -245,28 +158,8 @@
     }
 
     @Test
-    public void testSystemGalleryAppHasFullAccessToImages() throws Exception {
-        runDeviceTest("testSystemGalleryAppHasFullAccessToImages");
-    }
-
-    @Test
-    public void testSystemGalleryAppHasNoFullAccessToAudio() throws Exception {
-        runDeviceTest("testSystemGalleryAppHasNoFullAccessToAudio");
-    }
-
-    @Test
-    public void testSystemGalleryCanRenameImagesAndVideos() throws Exception {
-        runDeviceTest("testSystemGalleryCanRenameImagesAndVideos");
-    }
-
-    @Test
-    public void testManageExternalStorageCanCreateFilesAnywhere() throws Exception {
-        allowAppOps("android:manage_external_storage");
-        try {
-            runDeviceTest("testManageExternalStorageCanCreateFilesAnywhere");
-        } finally {
-            denyAppOps("android:manage_external_storage");
-        }
+    public void testCantRenameToTopLevelDirectory() throws Exception {
+        runDeviceTest("testCantRenameToTopLevelDirectory");
     }
 
     @Test
@@ -280,16 +173,6 @@
     }
 
     @Test
-    public void testManageExternalStorageReaddir() throws Exception {
-        allowAppOps("android:manage_external_storage");
-        try {
-            runDeviceTest("testManageExternalStorageReaddir");
-        } finally {
-            denyAppOps("android:manage_external_storage");
-        }
-    }
-
-    @Test
     public void testManageExternalStorageCanRenameOtherAppsContents() throws Exception {
         allowAppOps("android:manage_external_storage");
         try {
@@ -315,18 +198,8 @@
     }
 
     @Test
-    public void testCanCreateHiddenFile() throws Exception {
-        runDeviceTest("testCanCreateHiddenFile");
-    }
-
-    @Test
-    public void testCanRenameHiddenFile() throws Exception {
-        runDeviceTest("testCanRenameHiddenFile");
-    }
-
-    @Test
-    public void testHiddenDirectory() throws Exception {
-        runDeviceTest("testHiddenDirectory");
+    public void testCanWriteToDCIMCameraWithNomedia() throws Exception {
+        runDeviceTest("testCanWriteToDCIMCameraWithNomedia");
     }
 
     @Test
@@ -375,31 +248,16 @@
     }
 
     @Test
-    public void testQueryOtherAppsFiles() throws Exception {
-        runDeviceTest("testQueryOtherAppsFiles");
-    }
-
-    @Test
-    public void testSystemGalleryCanRenameImageAndVideoDirs() throws Exception {
-        runDeviceTest("testSystemGalleryCanRenameImageAndVideoDirs");
-    }
-
-    @Test
-    public void testCreateCanRestoreDeletedRowId() throws Exception {
-        runDeviceTest("testCreateCanRestoreDeletedRowId");
-    }
-
-    @Test
-    public void testRenameCanRestoreDeletedRowId() throws Exception {
-        runDeviceTest("testRenameCanRestoreDeletedRowId");
-    }
-
-    @Test
     public void testCantCreateOrRenameFileWithInvalidName() throws Exception {
         runDeviceTest("testCantCreateOrRenameFileWithInvalidName");
     }
 
     @Test
+    public void testRenameWithSpecialChars() throws Exception {
+        runDeviceTest("testRenameWithSpecialChars");
+    }
+
+    @Test
     public void testPendingFromFuse() throws Exception {
         runDeviceTest("testPendingFromFuse");
     }
@@ -420,28 +278,6 @@
     }
 
     @Test
-    public void testAccess_file() throws Exception {
-        grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
-        try {
-            runDeviceTest("testAccess_file");
-        } finally {
-            revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
-        }
-    }
-
-    @Test
-    public void testAccess_directory() throws Exception {
-        grantPermissions("android.permission.READ_EXTERNAL_STORAGE",
-                "android.permission.WRITE_EXTERNAL_STORAGE");
-        try {
-            runDeviceTest("testAccess_directory");
-        } finally {
-            revokePermissions("android.permission.READ_EXTERNAL_STORAGE",
-                    "android.permission.WRITE_EXTERNAL_STORAGE");
-        }
-    }
-
-    @Test
     public void testAndroidMedia() throws Exception {
         grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
         try {
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageInstantAppHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageInstantAppHostTest.java
index c97b41f..50fd029 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageInstantAppHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageInstantAppHostTest.java
@@ -16,12 +16,11 @@
 
 package android.scopedstorage.cts.host;
 
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.platform.test.annotations.AppModeInstant;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,14 +29,14 @@
  * Runs the ScopedStorageTest tests for an instant app.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class ScopedStorageInstantAppHostTest extends BaseHostJUnit4Test {
+public class ScopedStorageInstantAppHostTest extends BaseHostTestCase {
     /**
      * Runs the given phase of Test by calling into the device.
      * Throws an exception if the test phase fails.
      */
     protected void runDeviceTest(String phase) throws Exception {
-        assertTrue(runDeviceTests("android.scopedstorage.cts",
-                "android.scopedstorage.cts.ScopedStorageTest", phase));
+        assertThat(runDeviceTests("android.scopedstorage.cts",
+                "android.scopedstorage.cts.ScopedStorageTest", phase)).isTrue();
     }
 
     @Test
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
index 4596cab..28089d7 100644
--- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
@@ -20,6 +20,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA2;
 import static android.scopedstorage.cts.lib.TestUtils.STR_DATA1;
 import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2;
+import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
 import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
@@ -29,15 +30,17 @@
 import static android.scopedstorage.cts.lib.TestUtils.createImageEntryAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
 import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
+import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
 import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
+import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
 import static android.scopedstorage.cts.lib.TestUtils.installApp;
 import static android.scopedstorage.cts.lib.TestUtils.listAs;
 import static android.scopedstorage.cts.lib.TestUtils.openFileAs;
-import static android.scopedstorage.cts.lib.TestUtils.openWithMediaProvider;
 import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
@@ -53,13 +56,14 @@
 import static org.junit.Assert.fail;
 
 import android.Manifest;
+import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
-import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.provider.MediaStore;
 import android.scopedstorage.cts.lib.TestUtils;
 import android.system.ErrnoException;
@@ -117,6 +121,9 @@
     private static final TestApp TEST_APP_A = new TestApp("TestAppA",
             "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
 
+    private static final String[] SYSTEM_GALERY_APPOPS = {
+            AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
+
     /**
      * This method needs to be called once before running the whole test.
      */
@@ -133,7 +140,11 @@
     @After
     public void teardown() throws Exception {
         executeShellCommand("rm " + getShellFile());
-        MediaStore.scanFile(getContentResolver(), getShellFile());
+        try {
+            MediaStore.scanFile(getContentResolver(), getShellFile());
+        } catch (Exception ignored) {
+            //ignore MediaScanner exceptions
+        }
     }
 
     /**
@@ -274,7 +285,7 @@
         // can open file for read
         FileDescriptor fd = null;
         try {
-            executeShellCommand("touch " + existingFile);
+            executeShellCommand("touch %s", existingFile);
             MediaStore.scanFile(getContentResolver(), existingFile);
             fd = Os.open(existingFile.getPath(), OsConstants.O_RDONLY, /*mode*/ 0);
         } finally {
@@ -764,6 +775,96 @@
         }
     }
 
+    @Test
+    public void testLegacySystemGalleryCanRenameImagesAndVideosWithoutDbUpdates() throws Exception {
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+        final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME);
+        final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
+
+        try {
+            installApp(TEST_APP_A);
+
+            try {
+                allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+                // Create and write some data to the file
+                assertThat(createFileAs(TEST_APP_A, otherAppVideoFile.getPath())).isTrue();
+                try (final FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
+                    fos.write(BYTES_DATA1);
+                }
+
+                // Assert legacy system gallery can rename the file.
+                assertCanRenameFile(otherAppVideoFile, videoFile, false /* checkDatabase */);
+                assertFileContent(videoFile, BYTES_DATA1);
+                // Database was not updated.
+                assertThat(getFileRowIdFromDatabase(otherAppVideoFile)).isNotEqualTo(-1);
+                assertThat(getFileRowIdFromDatabase(videoFile)).isEqualTo(-1);
+            }
+            finally {
+                otherAppVideoFile.delete();
+                videoFile.delete();
+                denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+            }
+        } finally {
+            uninstallAppNoThrow(TEST_APP_A);
+        }
+    }
+
+    @Test
+    public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
+
+        final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME);
+        final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
+
+        try {
+            installApp(TEST_APP_A);
+
+            try {
+                allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+                // Create file of other app.
+                assertThat(createFileAs(TEST_APP_A, otherAppVideoFile.getPath())).isTrue();
+
+                // Check we cannot rename it.
+                assertThat(otherAppVideoFile.renameTo(videoFile)).isFalse();
+            }
+            finally {
+                otherAppVideoFile.delete();
+                videoFile.delete();
+                denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+            }
+        } finally {
+            uninstallAppNoThrow(TEST_APP_A);
+        }
+    }
+
+    @Test
+    public void testLegacyWESCanRenameImagesAndVideosWithDbUpdates_hasW() throws Exception {
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+        final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME);
+        final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
+
+        try {
+            installApp(TEST_APP_A);
+             // Create and write some data to the file
+             assertThat(createFileAs(TEST_APP_A, otherAppVideoFile.getPath())).isTrue();
+             try (final FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
+                 fos.write(BYTES_DATA1);
+             }
+
+             // Assert legacy WES can rename the file (including database updated).
+             assertCanRenameFile(otherAppVideoFile, videoFile);
+             assertFileContent(videoFile, BYTES_DATA1);
+        } finally {
+            otherAppVideoFile.delete();
+            videoFile.delete();
+            uninstallAppNoThrow(TEST_APP_A);
+        }
+    }
+
     private static void assertCanCreateFile(File file) throws IOException {
         if (file.exists()) {
             file.delete();
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index 9237046..07b30ed 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -120,6 +120,7 @@
     public static void setupDefaultDirectories() {
         for (File dir : getDefaultTopLevelDirs()) {
             dir.mkdir();
+            assertThat(dir.exists()).isTrue();
         }
     }
 
@@ -170,7 +171,8 @@
     /**
      * Executes a shell command.
      */
-    public static String executeShellCommand(String command) throws IOException {
+    public static String executeShellCommand(String pattern, Object...args) throws IOException {
+        String command = String.format(pattern, args);
         int attempt = 0;
         while (attempt++ < 5) {
             try {
@@ -752,7 +754,7 @@
      */
     public static void assertDirectoryContains(@NonNull File dir, File... expectedContent) {
         assertThat(dir.isDirectory()).isTrue();
-        assertThat(Arrays.asList(dir.listFiles())).containsAllIn(expectedContent);
+        assertThat(Arrays.asList(dir.listFiles())).containsAtLeastElementsIn(expectedContent);
     }
 
     public static File getExternalStorageDir() {
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index abf72f0..215d9f3 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -65,6 +65,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileSizeFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileUri;
+import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
 import static android.scopedstorage.cts.lib.TestUtils.getMoviesDir;
 import static android.scopedstorage.cts.lib.TestUtils.getMusicDir;
 import static android.scopedstorage.cts.lib.TestUtils.getNotificationsDir;
@@ -263,10 +264,14 @@
         assertCanCreateFile(new File(documentsDir, AUDIO_FILE_NAME));
         assertCanCreateFile(new File(documentsDir, IMAGE_FILE_NAME));
         assertCanCreateFile(new File(documentsDir, NONMEDIA_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, PLAYLIST_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, SUBTITLE_FILE_NAME));
         assertCanCreateFile(new File(documentsDir, VIDEO_FILE_NAME));
         assertCanCreateFile(new File(downloadDir, AUDIO_FILE_NAME));
         assertCanCreateFile(new File(downloadDir, IMAGE_FILE_NAME));
         assertCanCreateFile(new File(downloadDir, NONMEDIA_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, PLAYLIST_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, SUBTITLE_FILE_NAME));
         assertCanCreateFile(new File(downloadDir, VIDEO_FILE_NAME));
         assertCanCreateFile(new File(moviesDir, VIDEO_FILE_NAME));
         assertCanCreateFile(new File(moviesDir, SUBTITLE_FILE_NAME));
@@ -1637,6 +1642,33 @@
         }
     }
 
+    /**
+     * Test that we don't allow renaming to top level directory
+     */
+    @Test
+    public void testCantRenameToTopLevelDirectory() throws Exception {
+        final File topLevelDir1 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_1");
+        final File topLevelDir2 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_2");
+        final File nonTopLevelDir = new File(getDcimDir(), TEST_DIRECTORY_NAME);
+        try {
+            executeShellCommand("mkdir " + topLevelDir1.getAbsolutePath());
+            assertTrue(topLevelDir1.exists());
+
+            // We can't rename a top level directory to a top level directory
+            assertCantRenameDirectory(topLevelDir1, topLevelDir2, null);
+
+            // However, we can rename a top level directory to non-top level directory.
+            assertCanRenameDirectory(topLevelDir1, nonTopLevelDir, null, null);
+
+            // We can't rename a non-top level directory to a top level directory.
+            assertCantRenameDirectory(nonTopLevelDir, topLevelDir2, null);
+        } finally {
+            executeShellCommand("rmdir " + topLevelDir1.getAbsolutePath());
+            executeShellCommand("rmdir " + topLevelDir2.getAbsolutePath());
+            nonTopLevelDir.delete();
+        }
+    }
+
     @Test
     public void testManageExternalStorageCanCreateFilesAnywhere() throws Exception {
         pollForManageExternalStorageAllowed();
@@ -1848,6 +1880,42 @@
     }
 
     /**
+     * b/168830497: Test that app can write to file in DCIM/Camera even with .nomedia presence
+     */
+    @Test
+    public void testCanWriteToDCIMCameraWithNomedia() throws Exception {
+        final File cameraDir = new File(getDcimDir(), "Camera");
+        final File nomediaFile = new File(cameraDir, ".nomedia");
+        Uri targetUri = null;
+
+        try {
+            if (!cameraDir.exists()) {
+                assertTrue(cameraDir.mkdirs());
+            }
+            if (!nomediaFile.exists()) {
+                executeShellCommand("touch " + nomediaFile.getAbsolutePath());
+                assertTrue(nomediaFile.exists());
+            }
+
+            ContentValues values = new ContentValues();
+            values.put(MediaColumns.RELATIVE_PATH, "DCIM/Camera");
+            targetUri = getContentResolver().insert(getImageContentUri(), values, Bundle.EMPTY);
+            assertNotNull(targetUri);
+
+            try (ParcelFileDescriptor pfd =
+                         getContentResolver().openFileDescriptor(targetUri, "w")) {
+                assertThat(pfd).isNotNull();
+                Os.write(pfd.getFileDescriptor(), ByteBuffer.wrap(BYTES_DATA1));
+            }
+
+            assertFileContent(new File(getFilePathFromUri(targetUri)), BYTES_DATA1);
+        } finally {
+            deleteWithMediaProviderNoThrow(targetUri);
+            executeShellCommand("rm " + nomediaFile.getAbsolutePath());
+        }
+    }
+
+    /**
      * Test that only file manager and app that created the hidden file can list it.
      */
     @Test
@@ -2153,7 +2221,9 @@
             executeShellCommand("mkdir " + topLevelDir.getAbsolutePath());
             assertDirectoryAccess(topLevelDir, true, false);
 
-            assertCannotReadOrWrite(new File("/storage/emulated"));
+            // We can see "/storage/emulated" exists, but not read/write to it, since it's
+            // outside the scope of external storage.
+            assertAccess(new File("/storage/emulated"), true, false, false);
         } finally {
             uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
             executeShellCommand("rmdir " + topLevelDir.getAbsolutePath());
@@ -2467,6 +2537,47 @@
     }
 
     @Test
+    public void testRenameWithSpecialChars() throws Exception {
+        final String specialCharsSuffix = "'`~!@#$%^& ()_+-={}[];'.)";
+
+        final File fileSpecialChars =
+                new File(getDownloadDir(), NONMEDIA_FILE_NAME + specialCharsSuffix);
+
+        final File dirSpecialChars =
+                new File(getDownloadDir(), TEST_DIRECTORY_NAME + specialCharsSuffix);
+        final File file1 = new File(dirSpecialChars, NONMEDIA_FILE_NAME);
+        final File fileSpecialChars1 =
+                new File(dirSpecialChars, NONMEDIA_FILE_NAME + specialCharsSuffix);
+
+        final File renamedDir = new File(getDocumentsDir(), TEST_DIRECTORY_NAME);
+        final File file2 = new File(renamedDir, NONMEDIA_FILE_NAME);
+        final File fileSpecialChars2 =
+                new File(renamedDir, NONMEDIA_FILE_NAME + specialCharsSuffix);
+        try {
+            assertTrue(fileSpecialChars.createNewFile());
+            if (!dirSpecialChars.exists()) {
+                assertTrue(dirSpecialChars.mkdir());
+            }
+            assertTrue(file1.createNewFile());
+
+            // We can rename file name with special characters
+            assertCanRenameFile(fileSpecialChars, fileSpecialChars1);
+
+            // We can rename directory name with special characters
+            assertCanRenameDirectory(dirSpecialChars, renamedDir,
+                    new File[] {file1, fileSpecialChars1}, new File[] {file2, fileSpecialChars2});
+        } finally {
+            file1.delete();
+            file2.delete();
+            fileSpecialChars.delete();
+            fileSpecialChars1.delete();
+            fileSpecialChars2.delete();
+            dirSpecialChars.delete();
+            renamedDir.delete();
+        }
+    }
+
+    @Test
     public void testAndroidMedia() throws Exception {
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
 
@@ -2936,7 +3047,9 @@
 
     private static void assertCreateFilesAs(TestApp testApp, File... files) throws Exception {
         for (File file : files) {
-            assertThat(createFileAs(testApp, file.getPath())).isTrue();
+            assertFalse("File already exists: " + file, file.exists());
+            assertTrue("Failed to create file " + file + " on behalf of "
+                            + testApp.getPackageName(), createFileAs(testApp, file.getPath()));
         }
     }
 
@@ -2950,8 +3063,10 @@
     private static void assertCreatePublishedFilesAs(TestApp testApp, File... files)
             throws Exception {
         for (File file : files) {
-            assertThat(createFileAs(testApp, file.getPath())).isTrue();
-            assertNotNull(MediaStore.scanFile(getContentResolver(), file));
+            assertTrue("Failed to create published file " + file + " on behalf of "
+                    + testApp.getPackageName(), createFileAs(testApp, file.getPath()));
+            assertNotNull("Failed to scan " + file,
+                    MediaStore.scanFile(getContentResolver(), file));
         }
     }
 
@@ -2964,14 +3079,16 @@
     private static void assertCanDeletePathsAs(TestApp testApp, String... filePaths)
             throws Exception {
         for (String path: filePaths) {
-            assertTrue(deleteFileAs(testApp, path));
+            assertTrue("Failed to delete file " + path + " on behalf of "
+                    + testApp.getPackageName(), deleteFileAs(testApp, path));
         }
     }
 
     private static void assertCantDeletePathsAs(TestApp testApp, String... filePaths)
             throws Exception {
         for (String path: filePaths) {
-            assertFalse(deleteFileAs(testApp, path));
+            assertFalse("Deleting " + path + " on behalf of " + testApp.getPackageName()
+                            + " was expected to fail", deleteFileAs(testApp, path));
         }
     }
 
@@ -2991,7 +3108,8 @@
 
     private static void assertCanDeletePaths(String... filePaths) {
         for (String filePath : filePaths) {
-            assertTrue(new File(filePath).delete());
+            assertTrue("Failed to delete " + filePath,
+                    new File(filePath).delete());
         }
     }
 
diff --git a/hostsidetests/seccomp/TEST_MAPPING b/hostsidetests/seccomp/TEST_MAPPING
new file mode 100644
index 0000000..16b0945
--- /dev/null
+++ b/hostsidetests/seccomp/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSeccompHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/seccomp/app/assets/syscalls_allowed.json b/hostsidetests/seccomp/app/assets/syscalls_allowed.json
index 03ce72b..f3aa1b7 100644
--- a/hostsidetests/seccomp/app/assets/syscalls_allowed.json
+++ b/hostsidetests/seccomp/app/assets/syscalls_allowed.json
@@ -1,4 +1,4 @@
-# DO NOT MODIFY.  CHANGE gen_blacklist.py INSTEAD.
+# DO NOT MODIFY.  CHANGE gen_blocklist.py INSTEAD.
 {
   "arm": {
     "inotify_init": 316,
diff --git a/hostsidetests/seccomp/app/assets/syscalls_blocked.json b/hostsidetests/seccomp/app/assets/syscalls_blocked.json
index a53319b..9683cff 100644
--- a/hostsidetests/seccomp/app/assets/syscalls_blocked.json
+++ b/hostsidetests/seccomp/app/assets/syscalls_blocked.json
@@ -1,4 +1,4 @@
-# DO NOT MODIFY.  CHANGE gen_blacklist.py INSTEAD.
+# DO NOT MODIFY.  CHANGE gen_blocklist.py INSTEAD.
 {
   "arm": {
     "acct": 51,
diff --git a/hostsidetests/seccomp/app/gen_blacklist.py b/hostsidetests/seccomp/app/gen_blacklist.py
deleted file mode 100755
index e39fd9f..0000000
--- a/hostsidetests/seccomp/app/gen_blacklist.py
+++ /dev/null
@@ -1,189 +0,0 @@
-#!/usr/bin/env python3
-#
-# This script generates syscall name to number mapping for supported
-# architectures.  To update the output, runs:
-#
-#  $ app/gen_blacklist.py --allowed app/assets/syscalls_allowed.json \
-#      --blocked app/assets/syscalls_blocked.json
-#
-# Note that these are just syscalls that explicitly allowed and blocked in CTS
-# currently.
-#
-# TODO: Consider generating it in Android.mk/bp.
-
-import argparse
-import glob
-import json
-import os
-import subprocess
-
-_SUPPORTED_ARCHS = ['arm', 'arm64', 'x86', 'x86_64', 'mips', 'mips64']
-
-# Syscalls that are currently explicitly allowed in CTS
-_SYSCALLS_ALLOWED_IN_CTS = {
-    'openat': 'all',
-
-    # b/35034743 - do not remove test without reading bug.
-    'syncfs': 'arm64',
-
-    # b/35906875 - do not remove test without reading bug
-    'inotify_init': 'arm',
-}
-
-# Syscalls that are currently explicitly blocked in CTS
-_SYSCALLS_BLOCKED_IN_CTS = {
-    'acct': 'all',
-    'add_key': 'all',
-    'adjtimex': 'all',
-    'chroot': 'all',
-    'clock_adjtime': 'all',
-    'clock_settime': 'all',
-    'delete_module': 'all',
-    'init_module': 'all',
-    'keyctl': 'all',
-    'mount': 'all',
-    'reboot': 'all',
-    'setdomainname': 'all',
-    'sethostname': 'all',
-    'settimeofday': 'all',
-    'setfsgid': 'all',
-    'setfsuid': 'all',
-    'setgid': 'all',
-    'setgid32': 'x86,arm',
-    'setgroups': 'all',
-    'setgroups32': 'x86,arm',
-    'setregid': 'all',
-    'setregid32': 'x86,arm',
-    'setresgid': 'all',
-    'setresgid32': 'x86,arm',
-    'setreuid': 'all',
-    'setreuid32': 'x86,arm',
-    'setuid': 'all',
-    'setuid32': 'x86,arm',
-    'swapoff': 'all',
-    'swapoff': 'all',
-    'swapon': 'all',
-    'swapon': 'all',
-    'syslog': 'all',
-    'umount2': 'all',
-}
-
-def create_syscall_name_to_number_map(arch, names):
-  arch_config = {
-      'arm': {
-          'uapi_class': 'asm-arm',
-          'extra_cflags': [],
-      },
-      'arm64': {
-          'uapi_class': 'asm-arm64',
-          'extra_cflags': [],
-      },
-      'x86': {
-          'uapi_class': 'asm-x86',
-          'extra_cflags': ['-D__i386__'],
-      },
-      'x86_64': {
-          'uapi_class': 'asm-x86',
-          'extra_cflags': [],
-      },
-      'mips': {
-          'uapi_class': 'asm-mips',
-          'extra_cflags': ['-D_MIPS_SIM=_MIPS_SIM_ABI32'],
-      },
-      'mips64': {
-          'uapi_class': 'asm-mips',
-          'extra_cflags': ['-D_MIPS_SIM=_MIPS_SIM_ABI64'],
-      }
-  }
-
-  # Run preprocessor over the __NR_syscall symbols, including unistd.h,
-  # to get the actual numbers
-  # TODO: The following code is forked from bionic/libc/tools/genseccomp.py.
-  # Figure out if we can de-duplicate them crossing cts project boundary.
-  prefix = '__SECCOMP_'  # prefix to ensure no name collisions
-  kernel_uapi_path = os.path.join(os.getenv('ANDROID_BUILD_TOP'),
-                                  'bionic/libc/kernel/uapi')
-  cpp = subprocess.Popen(
-      [get_latest_clang_path(),
-       '-E', '-nostdinc',
-       '-I' + os.path.join(kernel_uapi_path,
-                           arch_config[arch]['uapi_class']),
-       '-I' + os.path.join(kernel_uapi_path)
-       ]
-      + arch_config[arch]['extra_cflags']
-      + ['-'],
-      universal_newlines=True,
-      stdin=subprocess.PIPE, stdout=subprocess.PIPE)
-  cpp.stdin.write('#include <asm/unistd.h>\n')
-  for name in names:
-    # In SYSCALLS.TXT, there are two arm-specific syscalls whose names start
-    # with __ARM__NR_. These we must simply write out as is.
-    if not name.startswith('__ARM_NR_'):
-      cpp.stdin.write(prefix + name + ', __NR_' + name + '\n')
-    else:
-      cpp.stdin.write(prefix + name + ', ' + name + '\n')
-  content = cpp.communicate()[0].split('\n')
-
-  # The input is now the preprocessed source file. This will contain a lot
-  # of junk from the preprocessor, but our lines will be in the format:
-  #
-  #     __SECCOMP_${NAME}, (0 + value)
-  syscalls = {}
-  for line in content:
-    if not line.startswith(prefix):
-      continue
-    # We might pick up extra whitespace during preprocessing, so best to strip.
-    name, value = [w.strip() for w in line.split(',')]
-    name = name[len(prefix):]
-    # Note that some of the numbers were expressed as base + offset, so we
-    # need to eval, not just int
-    value = eval(value)
-    if name in syscalls:
-      raise Exception('syscall %s is re-defined' % name)
-    syscalls[name] = value
-  return syscalls
-
-def get_latest_clang_path():
-  candidates = sorted(glob.glob(os.path.join(os.getenv('ANDROID_BUILD_TOP'),
-      'prebuilts/clang/host/linux-x86/clang-*')), reverse=True)
-  for clang_dir in candidates:
-    clang_exe = os.path.join(clang_dir, 'bin/clang')
-    if os.path.exists(clang_exe):
-      return clang_exe
-  raise FileNotFoundError('Cannot locate clang executable')
-
-def collect_syscall_names_for_arch(syscall_map, arch):
-  syscall_names = []
-  for syscall in syscall_map.keys():
-    if (arch in syscall_map[syscall] or
-        'all' == syscall_map[syscall]):
-      syscall_names.append(syscall)
-  return syscall_names
-
-def main():
-  parser = argparse.ArgumentParser('syscall name to number generator')
-  parser.add_argument('--allowed', metavar='path/to/json', type=str)
-  parser.add_argument('--blocked', metavar='path/to/json', type=str)
-  args = parser.parse_args()
-
-  allowed = {}
-  blocked = {}
-  for arch in _SUPPORTED_ARCHS:
-    blocked[arch] = create_syscall_name_to_number_map(
-        arch,
-        collect_syscall_names_for_arch(_SYSCALLS_BLOCKED_IN_CTS, arch))
-    allowed[arch] = create_syscall_name_to_number_map(
-        arch,
-        collect_syscall_names_for_arch(_SYSCALLS_ALLOWED_IN_CTS, arch))
-
-  msg_do_not_modify = '# DO NOT MODIFY.  CHANGE gen_blacklist.py INSTEAD.'
-  with open(args.allowed, 'w') as f:
-    print(msg_do_not_modify, file=f)
-    json.dump(allowed, f, sort_keys=True, indent=2)
-
-  with open(args.blocked, 'w') as f:
-    print(msg_do_not_modify, file=f)
-    json.dump(blocked, f, sort_keys=True, indent=2)
-
-if __name__ == '__main__':
-  main()
diff --git a/hostsidetests/seccomp/app/gen_blocklist.py b/hostsidetests/seccomp/app/gen_blocklist.py
new file mode 100755
index 0000000..588ebbb
--- /dev/null
+++ b/hostsidetests/seccomp/app/gen_blocklist.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+#
+# This script generates syscall name to number mapping for supported
+# architectures.  To update the output, runs:
+#
+#  $ app/gen_blocklist.py --allowed app/assets/syscalls_allowed.json \
+#      --blocked app/assets/syscalls_blocked.json
+#
+# Note that these are just syscalls that explicitly allowed and blocked in CTS
+# currently.
+#
+# TODO: Consider generating it in Android.mk/bp.
+
+import argparse
+import glob
+import json
+import os
+import subprocess
+
+_SUPPORTED_ARCHS = ['arm', 'arm64', 'x86', 'x86_64', 'mips', 'mips64']
+
+# Syscalls that are currently explicitly allowed in CTS
+_SYSCALLS_ALLOWED_IN_CTS = {
+    'openat': 'all',
+
+    # b/35034743 - do not remove test without reading bug.
+    'syncfs': 'arm64',
+
+    # b/35906875 - do not remove test without reading bug
+    'inotify_init': 'arm',
+}
+
+# Syscalls that are currently explicitly blocked in CTS
+_SYSCALLS_BLOCKED_IN_CTS = {
+    'acct': 'all',
+    'add_key': 'all',
+    'adjtimex': 'all',
+    'chroot': 'all',
+    'clock_adjtime': 'all',
+    'clock_settime': 'all',
+    'delete_module': 'all',
+    'init_module': 'all',
+    'keyctl': 'all',
+    'mount': 'all',
+    'reboot': 'all',
+    'setdomainname': 'all',
+    'sethostname': 'all',
+    'settimeofday': 'all',
+    'setfsgid': 'all',
+    'setfsuid': 'all',
+    'setgid': 'all',
+    'setgid32': 'x86,arm',
+    'setgroups': 'all',
+    'setgroups32': 'x86,arm',
+    'setregid': 'all',
+    'setregid32': 'x86,arm',
+    'setresgid': 'all',
+    'setresgid32': 'x86,arm',
+    'setreuid': 'all',
+    'setreuid32': 'x86,arm',
+    'setuid': 'all',
+    'setuid32': 'x86,arm',
+    'swapoff': 'all',
+    'swapoff': 'all',
+    'swapon': 'all',
+    'swapon': 'all',
+    'syslog': 'all',
+    'umount2': 'all',
+}
+
+def create_syscall_name_to_number_map(arch, names):
+  arch_config = {
+      'arm': {
+          'uapi_class': 'asm-arm',
+          'extra_cflags': [],
+      },
+      'arm64': {
+          'uapi_class': 'asm-arm64',
+          'extra_cflags': [],
+      },
+      'x86': {
+          'uapi_class': 'asm-x86',
+          'extra_cflags': ['-D__i386__'],
+      },
+      'x86_64': {
+          'uapi_class': 'asm-x86',
+          'extra_cflags': [],
+      },
+      'mips': {
+          'uapi_class': 'asm-mips',
+          'extra_cflags': ['-D_MIPS_SIM=_MIPS_SIM_ABI32'],
+      },
+      'mips64': {
+          'uapi_class': 'asm-mips',
+          'extra_cflags': ['-D_MIPS_SIM=_MIPS_SIM_ABI64'],
+      }
+  }
+
+  # Run preprocessor over the __NR_syscall symbols, including unistd.h,
+  # to get the actual numbers
+  # TODO: The following code is forked from bionic/libc/tools/genseccomp.py.
+  # Figure out if we can de-duplicate them crossing cts project boundary.
+  prefix = '__SECCOMP_'  # prefix to ensure no name collisions
+  kernel_uapi_path = os.path.join(os.getenv('ANDROID_BUILD_TOP'),
+                                  'bionic/libc/kernel/uapi')
+  cpp = subprocess.Popen(
+      [get_latest_clang_path(),
+       '-E', '-nostdinc',
+       '-I' + os.path.join(kernel_uapi_path,
+                           arch_config[arch]['uapi_class']),
+       '-I' + os.path.join(kernel_uapi_path)
+       ]
+      + arch_config[arch]['extra_cflags']
+      + ['-'],
+      universal_newlines=True,
+      stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+  cpp.stdin.write('#include <asm/unistd.h>\n')
+  for name in names:
+    # In SYSCALLS.TXT, there are two arm-specific syscalls whose names start
+    # with __ARM__NR_. These we must simply write out as is.
+    if not name.startswith('__ARM_NR_'):
+      cpp.stdin.write(prefix + name + ', __NR_' + name + '\n')
+    else:
+      cpp.stdin.write(prefix + name + ', ' + name + '\n')
+  content = cpp.communicate()[0].split('\n')
+
+  # The input is now the preprocessed source file. This will contain a lot
+  # of junk from the preprocessor, but our lines will be in the format:
+  #
+  #     __SECCOMP_${NAME}, (0 + value)
+  syscalls = {}
+  for line in content:
+    if not line.startswith(prefix):
+      continue
+    # We might pick up extra whitespace during preprocessing, so best to strip.
+    name, value = [w.strip() for w in line.split(',')]
+    name = name[len(prefix):]
+    # Note that some of the numbers were expressed as base + offset, so we
+    # need to eval, not just int
+    value = eval(value)
+    if name in syscalls:
+      raise Exception('syscall %s is re-defined' % name)
+    syscalls[name] = value
+  return syscalls
+
+def get_latest_clang_path():
+  candidates = sorted(glob.glob(os.path.join(os.getenv('ANDROID_BUILD_TOP'),
+      'prebuilts/clang/host/linux-x86/clang-*')), reverse=True)
+  for clang_dir in candidates:
+    clang_exe = os.path.join(clang_dir, 'bin/clang')
+    if os.path.exists(clang_exe):
+      return clang_exe
+  raise FileNotFoundError('Cannot locate clang executable')
+
+def collect_syscall_names_for_arch(syscall_map, arch):
+  syscall_names = []
+  for syscall in syscall_map.keys():
+    if (arch in syscall_map[syscall] or
+        'all' == syscall_map[syscall]):
+      syscall_names.append(syscall)
+  return syscall_names
+
+def main():
+  parser = argparse.ArgumentParser('syscall name to number generator')
+  parser.add_argument('--allowed', metavar='path/to/json', type=str)
+  parser.add_argument('--blocked', metavar='path/to/json', type=str)
+  args = parser.parse_args()
+
+  allowed = {}
+  blocked = {}
+  for arch in _SUPPORTED_ARCHS:
+    blocked[arch] = create_syscall_name_to_number_map(
+        arch,
+        collect_syscall_names_for_arch(_SYSCALLS_BLOCKED_IN_CTS, arch))
+    allowed[arch] = create_syscall_name_to_number_map(
+        arch,
+        collect_syscall_names_for_arch(_SYSCALLS_ALLOWED_IN_CTS, arch))
+
+  msg_do_not_modify = '# DO NOT MODIFY.  CHANGE gen_blocklist.py INSTEAD.'
+  with open(args.allowed, 'w') as f:
+    print(msg_do_not_modify, file=f)
+    json.dump(allowed, f, sort_keys=True, indent=2)
+
+  with open(args.blocked, 'w') as f:
+    print(msg_do_not_modify, file=f)
+    json.dump(blocked, f, sort_keys=True, indent=2)
+
+if __name__ == '__main__':
+  main()
diff --git a/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java b/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java
index 1f1772b..e670426 100644
--- a/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java
+++ b/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java
@@ -130,6 +130,11 @@
         assertSeccompFilter("media.extractor", PS_CMD, false);
     }
 
+    public void testMediaSwcodecHasSeccompFilter() throws DeviceNotAvailableException {
+        // non-mainline devices might not have this process
+        assertSeccompFilter("media.swcodec", PS_CMD, false, false /* mustHaveProcess */);
+    }
+
     public void testOmxHalHasSeccompFilter() throws DeviceNotAvailableException {
         assertSeccompFilter("media.codec", PS_CMD, false);
     }
diff --git a/hostsidetests/securitybulletin/AndroidTest.xml b/hostsidetests/securitybulletin/AndroidTest.xml
index 9a5c29d..cfdba48 100644
--- a/hostsidetests/securitybulletin/AndroidTest.xml
+++ b/hostsidetests/securitybulletin/AndroidTest.xml
@@ -23,8 +23,6 @@
         <option name="cleanup" value="true" />
         <!--__________________-->
         <!--    Utilities     -->
-        <option name="push" value="pacrunner->/data/local/tmp/pacrunner" />
-
         <option name="push" value="CVE-2016-8460->/data/local/tmp/CVE-2016-8460" />
         <option name="push" value="CVE-2016-8482->/data/local/tmp/CVE-2016-8482" />
         <option name="push" value="CVE-2016-6730->/data/local/tmp/CVE-2016-6730" />
@@ -187,7 +185,6 @@
         <!--__________________-->
         <!-- Bulletin 2018-07 -->
         <!-- Please add tests solely from this bulletin below to avoid merge conflict -->
-        <option name="push" value="CVE-2018-9428->/data/local/tmp/CVE-2018-9428" />
         <option name="push" value="CVE-2018-9424->/data/local/tmp/CVE-2018-9424" />
 
         <!--__________________-->
diff --git a/hostsidetests/securitybulletin/res/CVE-2018-9490.pac b/hostsidetests/securitybulletin/res/CVE-2018-9490.pac
deleted file mode 100644
index 999518a..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2018-9490.pac
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function FindProxyForURL(url, host){
-    alert("enter");
-    let arr = [];
-    arr[1000] = 0x1234;
-
-    arr.__defineGetter__(256, function () {
-            delete arr[256];
-            arr.unshift(1.1);
-            arr.length = 0;
-            });
-
-    Object.entries(arr).toString();
-    alert(JSON.stringify(entries));
-    return 0;
-}
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2045.pac b/hostsidetests/securitybulletin/res/CVE-2019-2045.pac
deleted file mode 100644
index a6b0166..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2045.pac
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function FindProxyForURL(url, host){
-    opttest();
-    opttest();
-    opttest();
-    opttest();
-    opttest();
-    opttest();
-    opttest();
-    opttest();
-    return "DIRECT";
-}
-
-function maxstring() {
-  // force TurboFan
-  try {} finally {}
-
-  var i = 'A'.repeat(2**28 - 16).indexOf("", 2**28);
-  i += 16; 
-  i >>= 28; 
-  i *= 1000000;
-  //i *= 3;
-  if (i >= 3) {
-    return 0;
-  } else {
-    var arr = [0.1, 0.2, 0.3, 0.4];
-    return arr[i];
-  }
-}
-
-function opttest() {
-  for (var j = 0; j < 100000; j++) {
-    var o = maxstring();
-    if (o == 0 || o == undefined) {
-      continue;
-    }
-    console.log(o);
-    return o;
-  }
-}
-
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2047.pac b/hostsidetests/securitybulletin/res/CVE-2019-2047.pac
deleted file mode 100644
index b70e24a..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2047.pac
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function FindProxyForURL(url, host){
-    for(var i = 0;i<0x10000;i++){
-        change_elements_kind(x);
-    }
-
-    for(var i = 0;i<0x10000;i++){
-        write_as_unboxed();
-    }
-
-    change_elements_kind(evil);
-
-    write_as_unboxed();
-
-    try{
-        evil[0].x;
-    }catch(e){
-    }
-    return "DIRECT";
-}
-
-function change_elements_kind(a){
-    a[0] = Array;
-}
-function read_as_unboxed(){
-    return evil[0];
-}
-
-function write_as_unboxed(){
-    evil[0] = 2.37341197482723178190425716704E-308; //0x00111111 00111111
-}
-
-change_elements_kind({});
-
-var map_manipulator = new Array(1.0,2.3);
-map_manipulator.x = 7;
-change_elements_kind(map_manipulator);
-
-map_manipulator.x = {};
-
-var evil = new Array(1.1,2.2);
-evil.x = {};
-
-var x = new Array({});
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2051.pac b/hostsidetests/securitybulletin/res/CVE-2019-2051.pac
deleted file mode 100644
index b24b160..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2051.pac
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function FindProxyForURL(url, host){
-    this.__defineGetter__("x", (a = (function f() { return; (function() {}); })()) => { });
-    x;
-    return "DIRECT";
-}
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2052.pac b/hostsidetests/securitybulletin/res/CVE-2019-2052.pac
deleted file mode 100644
index 670e870..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2052.pac
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function FindProxyForURL(url, host){
-    for(var i = 0;i < 0x1000;i++){
-        tt();
-    }
-
-    return "DIRECT";
-}
-
-function tt(){
-    var evil_o = {};
-    var reg = /abc/y;
-    var num = {};
-    num.toString = function(){
-	    change_to_dict();
-	    return 0x0;
-    }
-
-
-    function change_to_dict(){
-	    for(var i = 0;i < 0x100;i++){
-		    reg["a"+i.toString(16)] = i;
-	    }
-    }
-
-    evil_o.toString = function(){
-	    //change_to_dict();
-	    reg.lastIndex = num;
-	    return "abc".repeat(0x1000);
-    }
-
-    String.prototype.replace.call(evil_o,reg,function(){});
-}
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2097.pac b/hostsidetests/securitybulletin/res/CVE-2019-2097.pac
deleted file mode 100644
index 4880f54..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2097.pac
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function FindProxyForURL(url, host){
-    for (var  i = 0; i < 0x10000; i++){
-        f();
-    }
-    array[0] = double_arr;
-    f();
-    try {
-    double_arr[1].x;
-    }catch(e){}
-    return "DIRECT";
-}
-
-var double_arr = [1.1, 2.2];
-var array = [[0.1],[0.1],[0.1]];
-
-function f(){
-    double_arr[0] = 3.3;
-    for(var i = 0; i < array.length; i++){
-        array[i][0] = {"abcd":0x4321};
-    }
-    double_arr[1] = 6.176516726456e-312;
-}
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2130.pac b/hostsidetests/securitybulletin/res/CVE-2019-2130.pac
deleted file mode 100644
index 77a0cb5..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2130.pac
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function FindProxyForURL(url, host){
-    function opt() {
-        opt['x'] = 1.1;
-        try {
-            Object.create(object);
-        } catch (e) {
-        }
-
-        for (let i = 0; i < 100000; i++) {
-
-        }
-    }
-
-    opt();
-    object = opt;
-    opt();
-
-    return "DIRECT";
-}
-
-var object;
diff --git a/hostsidetests/securitybulletin/res/bug_138441919.pac b/hostsidetests/securitybulletin/res/bug_138441919.pac
deleted file mode 100644
index 006fb6a..0000000
--- a/hostsidetests/securitybulletin/res/bug_138441919.pac
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function FindProxyForURL(url, host){
-    Object.defineProperty(Promise, Symbol.species, { value: 0 });
-    var p = new Promise(function() {});
-    p.then();
-    return "DIRECT";
-}
diff --git a/hostsidetests/securitybulletin/res/bug_139806216.pac b/hostsidetests/securitybulletin/res/bug_139806216.pac
deleted file mode 100644
index 256108d..0000000
--- a/hostsidetests/securitybulletin/res/bug_139806216.pac
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function FindProxyForURL(url, host){
-    var x = new ArrayBuffer(1);
-    return "DIRECT";
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
index 1a7e5b6..b25b133 100755
--- a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
@@ -43,12 +43,11 @@
 
     // Write the header
     outMsg->header.type = msg.header.type;
+    outMsg->header.seq = msg.header.seq;
 
     // Write the body
     switch(msg.header.type) {
         case InputMessage::Type::KEY: {
-            // uint32_t seq
-            outMsg->body.key.seq = msg.body.key.seq;
             // int32_t eventId
             outMsg->body.key.eventId = msg.body.key.eventId;
             // nsecs_t eventTime
@@ -78,8 +77,6 @@
             break;
         }
         case InputMessage::Type::MOTION: {
-            // uint32_t seq
-            outMsg->body.motion.seq = msg.body.motion.seq;
             // int32_t eventId
             outMsg->body.motion.eventId = msg.body.key.eventId;
             // nsecs_t eventTime
@@ -108,14 +105,18 @@
             outMsg->body.motion.edgeFlags = msg.body.motion.edgeFlags;
             // nsecs_t downTime
             outMsg->body.motion.downTime = msg.body.motion.downTime;
-            // float xScale
-            outMsg->body.motion.xScale = msg.body.motion.xScale;
-            // float yScale
-            outMsg->body.motion.yScale = msg.body.motion.yScale;
-            // float xOffset
-            outMsg->body.motion.xOffset = msg.body.motion.xOffset;
-            // float yOffset
-            outMsg->body.motion.yOffset = msg.body.motion.yOffset;
+            // float dsdx
+            outMsg->body.motion.dsdx = msg.body.motion.dsdx;
+            // float dtdx
+            outMsg->body.motion.dtdx = msg.body.motion.dtdx;
+            // float dtdy
+            outMsg->body.motion.dtdy = msg.body.motion.dtdy;
+            // float dsdy
+            outMsg->body.motion.dsdy = msg.body.motion.dsdy;
+            // float tx
+            outMsg->body.motion.tx = msg.body.motion.tx;
+            // float ty
+            outMsg->body.motion.ty = msg.body.motion.ty;
             // float xPrecision
             outMsg->body.motion.xPrecision = msg.body.motion.xPrecision;
             // float yPrecision
@@ -144,12 +145,10 @@
             break;
         }
         case InputMessage::Type::FINISHED: {
-            outMsg->body.finished.seq = msg.body.finished.seq;
             outMsg->body.finished.handled = msg.body.finished.handled;
             break;
         }
         case InputMessage::Type::FOCUS: {
-            outMsg->body.focus.seq = msg.body.focus.seq;
             outMsg->body.focus.eventId = msg.body.focus.eventId;
             outMsg->body.focus.hasFocus = msg.body.focus.hasFocus;
             outMsg->body.focus.inTouchMode = msg.body.focus.inTouchMode;
@@ -161,7 +160,7 @@
 /**
  * Return false if vulnerability is found for a given message type
  */
-static bool checkMessage(sp<InputChannel> server, sp<InputChannel> client, InputMessage::Type type) {
+static bool checkMessage(InputChannel& server, InputChannel& client, InputMessage::Type type) {
     InputMessage serverMsg;
     // Set all potentially uninitialized bytes to 1, for easier comparison
 
@@ -170,14 +169,14 @@
     if (type == InputMessage::Type::MOTION) {
         serverMsg.body.motion.pointerCount = MAX_POINTERS;
     }
-    status_t result = server->sendMessage(&serverMsg);
+    status_t result = server.sendMessage(&serverMsg);
     if (result != OK) {
         ALOGE("Could not send message to the input channel");
         return false;
     }
 
     InputMessage clientMsg;
-    result = client->receiveMessage(&clientMsg);
+    result = client.receiveMessage(&clientMsg);
     if (result != OK) {
         ALOGE("Could not receive message from the input channel");
         return false;
@@ -187,11 +186,6 @@
         return false;
     }
 
-    if (clientMsg.header.padding != 0) {
-        ALOGE("Found padding to be uninitialized");
-        return false;
-    }
-
     InputMessage sanitizedClientMsg;
     sanitizeMessage(clientMsg, &sanitizedClientMsg);
     if (memcmp(&clientMsg, &sanitizedClientMsg, clientMsg.size()) != 0) {
@@ -213,7 +207,7 @@
  * Do this for all message types
  */
 int main() {
-    sp<InputChannel> server, client;
+    std::unique_ptr<InputChannel> server, client;
 
     status_t result = InputChannel::openInputChannelPair("channel name", server, client);
     if (result != OK) {
@@ -228,7 +222,7 @@
         InputMessage::Type::FOCUS,
     };
     for (InputMessage::Type type : types) {
-        bool success = checkMessage(server, client, type);
+        bool success = checkMessage(*server, *client, type);
         if (!success) {
             ALOGE("Check message failed for type %i", type);
             return EXIT_VULNERABLE;
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3913/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3913/poc.cpp
index 5d45862..7243c16 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3913/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3913/poc.cpp
@@ -44,6 +44,8 @@
 
   status_t setDataSource(const sp<IDataSource> &source) { return OK; }
 
+  status_t setDataSource(const String8& rtpParams) { return OK; }
+
   status_t setVideoSurfaceTexture(
       const sp<IGraphicBufferProducer> &bufferProducer) {
     return OK;
@@ -164,4 +166,4 @@
   player->setNextPlayer(localPlayer);
 
   return EXIT_SUCCESS;
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
index d9acb35..8c1f140 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
@@ -16,23 +16,31 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <stdlib.h>
+
+#include <android/media/BnEffectClient.h>
+#include <android/media/IEffect.h>
 #include <media/AudioSystem.h>
 #include <hardware/audio_effect.h>
 #include <media/IAudioFlinger.h>
-#include <media/IEffect.h>
-#include <media/IEffectClient.h>
 
 using namespace android;
+using media::IEffect;
 
-struct EffectClient : public android::BnEffectClient {
+struct EffectClient : public media::BnEffectClient {
   EffectClient() {}
-  virtual void controlStatusChanged(bool controlGranted __unused) {}
-  virtual void enableStatusChanged(bool enabled __unused) {}
-  virtual void commandExecuted(uint32_t cmdCode __unused,
-                               uint32_t cmdSize __unused,
-                               void *pCmdData __unused,
-                               uint32_t replySize __unused,
-                               void *pReplyData __unused) {}
+  virtual binder::Status controlStatusChanged(bool controlGranted __unused) {
+    return binder::Status::ok();
+  }
+
+  virtual binder::Status enableStatusChanged(bool enabled __unused) {
+    return binder::Status::ok();
+  }
+
+  virtual binder::Status commandExecuted(int32_t cmdCode __unused,
+      const std::vector<uint8_t>& cmdData __unused,
+      const std::vector<uint8_t>& replyData __unused) {
+    return binder::Status::ok();
+  }
 };
 
 sp<IEffect> gEffect;
@@ -63,14 +71,8 @@
   int i, enabled;
   status_t err;
 
-  uint32_t cmdCode, cmdSize, pReplySize;
-  int *pCmdData, *pReplyData;
-
-  cmdCode = EFFECT_CMD_GET_CONFIG;
-  cmdSize = 0;
-  pReplySize = sizeof(effect_config_t);
-  pCmdData = (int *)malloc(cmdSize);
-  pReplyData = (int *)malloc(pReplySize);
+  std::vector<uint8_t> cmdData;
+  std::vector<uint8_t> replyData;
 
   gEffect = NULL;
   pthread_t pt;
@@ -85,8 +87,12 @@
       return -1;
     }
     pthread_create(&pt, NULL, disconnectThread, NULL);
-    err = gEffect->command(cmdCode, cmdSize, (void *)pCmdData, &pReplySize,
-                           (void *)pReplyData);
+    binder::Status status = gEffect->command(EFFECT_CMD_GET_CONFIG, cmdData,
+                                             sizeof(effect_config_t),
+                                             &replyData, &err);
+    if (!status.isOk()) {
+      err = status.transactionError();
+    }
     usleep(50);
   }
   sleep(2);
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/Android.bp
deleted file mode 100644
index a4c4f0a..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-cc_test {
-    name: "CVE-2018-9428",
-    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
-    srcs: [
-        "poc.cpp",
-    ],
-    shared_libs: [
-        "libmedia",
-        "libutils",
-        "libbinder",
-        "libaaudio_internal",
-    ],
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/poc.cpp
deleted file mode 100644
index cf21dbc..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/poc.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "../includes/common.h"
-#include "binding/IAAudioService.h"
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-
-using namespace android;
-using namespace aaudio;
-
-typedef struct _thread_args {
-  aaudio_handle_t aaudioHandle;
-  sp<IAAudioService> aas;
-} thread_args;
-
-static void *closeStreamThread(void *arg) {
-  thread_args *threadArgs = (thread_args *)arg;
-  if (threadArgs) {
-    if (threadArgs->aas) {
-      threadArgs->aas->closeStream(threadArgs->aaudioHandle);
-    }
-  }
-  return nullptr;
-}
-
-static void *startStreamThread(void *arg) {
-  thread_args *threadArgs = (thread_args *)arg;
-  if (threadArgs) {
-    if (threadArgs->aas) {
-      threadArgs->aas->startStream(threadArgs->aaudioHandle);
-    }
-  }
-  return nullptr;
-}
-
-int main() {
-  thread_args targs;
-
-  sp<IServiceManager> sm = defaultServiceManager();
-  sp<IBinder> binder = sm->getService(String16("media.aaudio"));
-  targs.aas = interface_cast<IAAudioService>(binder);
-  if (!(targs.aas)) {
-    return EXIT_FAILURE;
-  }
-  aaudio::AAudioStreamRequest request;
-  request.getConfiguration().setSharingMode(AAUDIO_SHARING_MODE_SHARED);
-  request.getConfiguration().setDeviceId(0);
-  request.getConfiguration().setSampleRate(AAUDIO_UNSPECIFIED);
-
-  time_t currentTime = start_timer();
-  while (timer_active(currentTime)) {
-    pthread_t pt[2];
-
-    aaudio::AAudioStreamConfiguration configurationOutput;
-    targs.aaudioHandle = targs.aas->openStream(request, configurationOutput);
-    pthread_create(&pt[0], nullptr, closeStreamThread,
-                   &targs); /* close stream */
-    pthread_create(&pt[1], nullptr, startStreamThread,
-                   &targs); /* start stream */
-
-    sleep(5);
-  }
-  return EXIT_SUCCESS;
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/pac/Android.bp b/hostsidetests/securitybulletin/securityPatch/pac/Android.bp
deleted file mode 100644
index 2d664aa..0000000
--- a/hostsidetests/securitybulletin/securityPatch/pac/Android.bp
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License
-
-cc_test {
-    name: "pacrunner",
-    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
-    srcs: ["pac.cpp"],
-    include_dirs: ["external/chromium-libpac/includes"],
-    shared_libs: ["libpac"],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/pac/pac.cpp b/hostsidetests/securitybulletin/securityPatch/pac/pac.cpp
deleted file mode 100644
index 393848d..0000000
--- a/hostsidetests/securitybulletin/securityPatch/pac/pac.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <proxy_resolver_v8_wrapper.h>
-#include <sys/types.h>
-#include <string.h>
-#include <codecvt>
-#include <fstream>
-#include <iostream>
-
-const char16_t* spec = u"";
-const char16_t* host = u"";
-
-int main(int argc, char *argv[]) {
-  if (argc != 2) {
-    std::cout << "incorrect number of arguments" << std::endl;
-    std::cout << "usage: ./pacrunner mypac.pac" << std::endl;
-    return EXIT_FAILURE;
-  }
-
-  ProxyResolverV8Handle* handle = ProxyResolverV8Handle_new();
-
-  std::ifstream t;
-  t.open(argv[1]);
-  if (t.rdstate() != std::ifstream::goodbit) {
-    std::cout << "error opening file" << std::endl;
-    return EXIT_FAILURE;
-  }
-  t.seekg(0, std::ios::end);
-  size_t size = t.tellg();
-  // allocate an extra byte for the null terminator
-  char* raw = (char*)calloc(size + 1, sizeof(char));
-  t.seekg(0);
-  t.read(raw, size);
-  std::string u8Script(raw);
-  std::u16string u16Script = std::wstring_convert<
-        std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(u8Script);
-
-  ProxyResolverV8Handle_SetPacScript(handle, u16Script.data());
-  ProxyResolverV8Handle_GetProxyForURL(handle, spec, host);
-
-  ProxyResolverV8Handle_delete(handle);
-  return EXIT_SUCCESS;
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
index 0605b39..19ab3b5 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
@@ -453,23 +453,6 @@
     }
 
     /**
-     * Runs the pacrunner utility against a given proxyautoconfig file, asserting that it doesn't
-     * crash
-     * @param pacName the name of the proxy autoconfig script from the /res folder
-     * @param device device to be ran on
-     */
-    public static int runProxyAutoConfig(String pacName, ITestDevice device) throws Exception {
-        pacName += ".pac";
-        String targetPath = TMP_PATH + pacName;
-        AdbUtils.pushResource("/" + pacName, targetPath, device);
-        runPocAssertNoCrashes(
-                "pacrunner", device, targetPath,
-                new CrashUtils.Config().setProcessPatterns("pacrunner"));
-        runCommandLine("rm " + targetPath, device);
-        return 0; // b/157172329 fix tests that manually check the result; remove return statement
-    }
-
-    /**
      * Runs the poc binary and asserts that there are no security crashes that match the expected
      * process pattern.
      * @param pocName a string path to poc from the /res folder
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc18_10.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc18_10.java
index ef5b726..45cb327 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc18_10.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc18_10.java
@@ -42,14 +42,4 @@
         }
         AdbUtils.runCommandLine("rm -rf /sdcard/Android/data/CVE-2018-9515", getDevice());
     }
-
-    /**
-     *  b/111274046
-     */
-    @Test
-    @SecurityTest
-    public void testPocCVE_2018_9490() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2018-9490", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_05.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_05.java
index fd3b638..ae739f5 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_05.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_05.java
@@ -25,37 +25,6 @@
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class Poc19_05 extends SecurityTestCase {
-
-    /**
-     * b/129556464
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-05")
-    public void testPocCVE_2019_2052() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2052", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-
-    /**
-     * b/129556111
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-05")
-    public void testPocCVE_2019_2045() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2045", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-
-    /*
-     * b/129556718
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-05")
-    public void testPocCVE_2019_2047() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2047", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-
     /**
      * CVE-2019-2257
      */
@@ -67,14 +36,4 @@
         assertFalse(result.contains(
                             "permission com.qualcomm.permission.USE_QTI_TELEPHONY_SERVICE"));
     }
-
-    /**
-     * b/117555811
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-05")
-    public void testPocCVE_2019_2051() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2051", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_06.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_06.java
deleted file mode 100644
index 67986fe..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_06.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import static org.junit.Assert.*;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class Poc19_06 extends SecurityTestCase {
-
-    /**
-     * b/129556445
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-06")
-    public void testPocCVE_2019_2097() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2097", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_08.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_08.java
deleted file mode 100644
index c2ce29d..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_08.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import static org.junit.Assert.*;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class Poc19_08 extends SecurityTestCase {
-
-    /**
-     * b/129556445
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-08")
-    public void testPocCVE_2019_2130() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2130", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_11.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_11.java
deleted file mode 100644
index a79e2b1..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_11.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import static org.junit.Assert.*;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class Poc19_11 extends SecurityTestCase {
-
-    /**
-     * b/138441919
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-11")
-    public void testPocBug_138441919() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("bug_138441919", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-
-    /**
-     * b/139806216
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-11")
-    public void testPocBug_139806216() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("bug_139806216", getDevice());
-        assertTrue(code != 139 && code != 135); // 128 + signal 11, 128 + signal 7
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java b/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
index f62bc8f..11f36bc 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
@@ -45,17 +45,6 @@
      ******************************************************************************/
 
     /**
-     * b/74122779
-     * Vulnerability Behaviour: SIGABRT in audioserver
-     */
-    @SecurityTest(minPatchLevel = "2018-07")
-    @Test
-    public void testPocCVE_2018_9428() throws Exception {
-        String signals[] = {CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT};
-        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig("CVE-2018-9428", getDevice());
-    }
-
-    /**
      * b/64340921
      * Vulnerability Behaviour: SIGABRT in audioserver
      */
diff --git a/hostsidetests/securitybulletin/test-apps/launchanywhere/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/launchanywhere/AndroidManifest.xml
index 1553c92..cc95d29 100644
--- a/hostsidetests/securitybulletin/test-apps/launchanywhere/AndroidManifest.xml
+++ b/hostsidetests/securitybulletin/test-apps/launchanywhere/AndroidManifest.xml
@@ -15,30 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.security.cts.launchanywhere"
-    android:versionCode="1"
-    android:versionName="1.0">
+     package="com.android.security.cts.launchanywhere"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <application android:label="LaunchAnyWhere Exploitation App">
-        <activity android:name=".StartExploit">
+        <activity android:name=".StartExploit"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <service android:name=".AuthenticatorService"
-            android:enabled="true"
-            android:exported="true">
+             android:enabled="true"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/launchanywhere_authenticator" />
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/launchanywhere_authenticator"/>
         </service>
 
     </application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml b/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
index b70c972..be53c0c 100644
--- a/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
+++ b/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
@@ -15,36 +15,34 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.google.android.cts.deviceowner" >
+     package="com.google.android.cts.deviceowner">
 
-    <application
-        android:label="Privacy Settings for Device Owner CTS host side app"
-        android:testOnly="true">
+    <application android:label="Privacy Settings for Device Owner CTS host side app"
+         android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name="com.google.android.cts.deviceowner.WorkPolicyInfoActivity"
-            android:exported="true"
-            android:launchMode="singleTask">
+        <activity android:name="com.google.android.cts.deviceowner.WorkPolicyInfoActivity"
+             android:exported="true"
+             android:launchMode="singleTask">
             <intent-filter>
                 <category android:name="android.intent.category.DEFAULT"/>
                 <action android:name="android.settings.SHOW_WORK_POLICY_INFO"/>
             </intent-filter>
         </activity>
 
-        <receiver
-            android:name="com.google.android.cts.deviceowner.DeviceOwnerTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.google.android.cts.deviceowner.DeviceOwnerTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.google.android.cts.deviceowner"
-                     android:label="Privacy Settings for Device Owner CTS tests"/>
+         android:targetPackage="com.google.android.cts.deviceowner"
+         android:label="Privacy Settings for Device Owner CTS tests"/>
 </manifest>
diff --git a/hostsidetests/signedconfig/app/version1_instant_AndroidManifest.xml b/hostsidetests/signedconfig/app/version1_instant_AndroidManifest.xml
index c5adf51..9ebeb87 100755
--- a/hostsidetests/signedconfig/app/version1_instant_AndroidManifest.xml
+++ b/hostsidetests/signedconfig/app/version1_instant_AndroidManifest.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" ?>
+<?xml version="1.0" encoding="utf-8"?>
 <!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,25 +13,26 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.signedconfig.app"
-    android:versionCode="1"
-    android:versionName="1">
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.cts.signedconfig.app"
+     android:versionCode="1"
+     android:versionName="1">
   <application>
     <meta-data android:name="android.settings.global"
-               android:value="@string/signed_config_v1"/>
+         android:value="@string/signed_config_v1"/>
     <meta-data android:name="android.settings.global.signature"
-               android:value="@string/signed_config_signature_v1"/>
+         android:value="@string/signed_config_signature_v1"/>
 
-    <activity android:name=".Empty">
+    <activity android:name=".Empty"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.intent.action.VIEW" />
-        <category android:name="android.intent.category.DEFAULT" />
-        <category android:name="android.intent.category.BROWSABLE" />
-        <data android:scheme="https" />
-        <data android:host="cts.android.com" />
-        <data android:path="/signedconfig" />
+        <action android:name="android.intent.action.VIEW"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <category android:name="android.intent.category.BROWSABLE"/>
+        <data android:scheme="https"/>
+        <data android:host="cts.android.com"/>
+        <data android:path="/signedconfig"/>
       </intent-filter>
     </activity>
 
diff --git a/hostsidetests/signedconfig/app/version2_instant_AndroidManifest.xml b/hostsidetests/signedconfig/app/version2_instant_AndroidManifest.xml
index b2d2e56..5010ce3 100755
--- a/hostsidetests/signedconfig/app/version2_instant_AndroidManifest.xml
+++ b/hostsidetests/signedconfig/app/version2_instant_AndroidManifest.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" ?>
+<?xml version="1.0" encoding="utf-8"?>
 <!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,25 +13,26 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.signedconfig.app"
-    android:versionCode="2"
-    android:versionName="2">
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.cts.signedconfig.app"
+     android:versionCode="2"
+     android:versionName="2">
   <application>
     <meta-data android:name="android.settings.global"
-               android:value="@string/signed_config_v2"/>
+         android:value="@string/signed_config_v2"/>
     <meta-data android:name="android.settings.global.signature"
-               android:value="@string/signed_config_signature_v2"/>
+         android:value="@string/signed_config_signature_v2"/>
 
-    <activity android:name=".Empty">
+    <activity android:name=".Empty"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.intent.action.VIEW" />
-        <category android:name="android.intent.category.DEFAULT" />
-        <category android:name="android.intent.category.BROWSABLE" />
-        <data android:scheme="https" />
-        <data android:host="cts.android.com" />
-        <data android:path="/signedconfig" />
+        <action android:name="android.intent.action.VIEW"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <category android:name="android.intent.category.BROWSABLE"/>
+        <data android:scheme="https"/>
+        <data android:host="cts.android.com"/>
+        <data android:path="/signedconfig"/>
       </intent-filter>
     </activity>
 
diff --git a/hostsidetests/stagedinstall/app/AndroidManifest.xml b/hostsidetests/stagedinstall/app/AndroidManifest.xml
index ebe83e6..679c55d 100644
--- a/hostsidetests/stagedinstall/app/AndroidManifest.xml
+++ b/hostsidetests/stagedinstall/app/AndroidManifest.xml
@@ -15,38 +15,31 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.tests.stagedinstall" >
+     package="com.android.tests.stagedinstall">
 
     <queries>
-        <package android:name="com.android.cts.ctsshim" />
+        <package android:name="com.android.cts.ctsshim"/>
     </queries>
 
     <application>
         <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <!-- This activity is necessary to register the test app as the default home activity (i.e.
-             to receive SESSION_COMMITTED broadcasts.) -->
-        <activity android:name=".LauncherActivity">
+                         to receive SESSION_COMMITTED broadcasts.) -->
+        <activity android:name=".LauncherActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
-                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <receiver android:name="com.android.tests.stagedinstall.SessionUpdateBroadcastReceiver">
-            <intent-filter>
-                <action android:name="android.content.pm.action.SESSION_UPDATED"/>
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.content.pm.action.SESSION_COMMITTED"/>
-            </intent-filter>
-        </receiver>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.tests.stagedinstall"
-                     android:label="StagedInstall Test"/>
+         android:targetPackage="com.android.tests.stagedinstall"
+         android:label="StagedInstall Test"/>
 </manifest>
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
index 38ae802..f8eabdb 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
@@ -38,8 +38,6 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.util.concurrent.TimeUnit;
-
 /**
  * These tests use a similar structure to {@link StagedInstallTest}. See
  * {@link StagedInstallTest} documentation for reference.
@@ -67,53 +65,38 @@
                 .dropShellPermissionIdentity();
     }
 
-    @Before
-    public void clearBroadcastReceiver() {
-        SessionUpdateBroadcastReceiver.sessionBroadcasts.clear();
-    }
-
     @Test
     public void testRejectsApexWithAdditionalFile_Commit() throws Exception {
         int sessionId = stageApex("com.android.apex.cts.shim.v2_additional_file.apex");
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
+        PackageInstaller.SessionInfo info = InstallUtils.waitForSession(sessionId);
         assertThat(info).isStagedSessionFailed();
     }
 
     @Test
     public void testRejectsApexWithAdditionalFolder_Commit() throws Exception {
         int sessionId = stageApex("com.android.apex.cts.shim.v2_additional_folder.apex");
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
+        PackageInstaller.SessionInfo info = InstallUtils.waitForSession(sessionId);
         assertThat(info).isStagedSessionFailed();
     }
 
     @Test
     public void testRejectsApexWithPostInstallHook_Commit() throws Exception {
         int sessionId = stageApex("com.android.apex.cts.shim.v2_with_post_install_hook.apex");
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
+        PackageInstaller.SessionInfo info = InstallUtils.waitForSession(sessionId);
         assertThat(info).isStagedSessionFailed();
     }
 
     @Test
     public void testRejectsApexWithPreInstallHook_Commit() throws Exception {
         int sessionId = stageApex("com.android.apex.cts.shim.v2_with_pre_install_hook.apex");
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
+        PackageInstaller.SessionInfo info = InstallUtils.waitForSession(sessionId);
         assertThat(info).isStagedSessionFailed();
     }
 
     @Test
     public void testRejectsApexWrongSHA_Commit() throws Exception {
         int sessionId = stageApex("com.android.apex.cts.shim.v2_wrong_sha.apex");
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
+        PackageInstaller.SessionInfo info = InstallUtils.waitForSession(sessionId);
         assertThat(info).isStagedSessionFailed();
     }
 
@@ -128,8 +111,9 @@
         int sessionId = Install.single(apexTestApp).setStaged().createSession();
         try (PackageInstaller.Session session =
                      InstallUtils.openPackageInstallerSession(sessionId)) {
-            session.commit(LocalIntentSender.getIntentSender());
-            Intent result = LocalIntentSender.getIntentSenderResult();
+            LocalIntentSender sender = new LocalIntentSender();
+            session.commit(sender.getIntentSender());
+            Intent result = sender.getResult();
             InstallUtils.assertStatusSuccess(result);
             return sessionId;
         }
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/PackageInstallerSessionInfoSubject.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/PackageInstallerSessionInfoSubject.java
index e78e5f3..4186ba1 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/PackageInstallerSessionInfoSubject.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/PackageInstallerSessionInfoSubject.java
@@ -26,10 +26,12 @@
 
 final class PackageInstallerSessionInfoSubject extends
         Subject<PackageInstallerSessionInfoSubject, PackageInstaller.SessionInfo> {
+    private final PackageInstaller.SessionInfo mActual;
 
     private PackageInstallerSessionInfoSubject(FailureMetadata failureMetadata,
             @Nullable PackageInstaller.SessionInfo subject) {
         super(failureMetadata, subject);
+        mActual = subject;
     }
 
     private static Subject.Factory<PackageInstallerSessionInfoSubject,
@@ -50,18 +52,15 @@
     }
 
     public void isStagedSessionReady() {
-        check().withMessage(failureMessage("in state READY")).that(
-                getSubject().isStagedSessionReady()).isTrue();
+        check(failureMessage("in state READY")).that(mActual.isStagedSessionReady()).isTrue();
     }
 
     public void isStagedSessionApplied() {
-        check().withMessage(failureMessage("in state APPLIED")).that(
-                getSubject().isStagedSessionApplied()).isTrue();
+        check(failureMessage("in state APPLIED")).that(mActual.isStagedSessionApplied()).isTrue();
     }
 
     public void isStagedSessionFailed() {
-        check().withMessage(failureMessage("in state FAILED")).that(
-                getSubject().isStagedSessionFailed()).isTrue();
+        check(failureMessage("in state FAILED")).that(mActual.isStagedSessionFailed()).isTrue();
     }
 
     private String failureMessage(String suffix) {
@@ -69,12 +68,11 @@
     }
 
     private String subjectAsString() {
-        PackageInstaller.SessionInfo session = getSubject();
-        return "{" + "appPackageName = " + session.getAppPackageName() + "; "
-                + "sessionId = " + session.getSessionId() + "; "
-                + "isStagedSessionReady = " + session.isStagedSessionReady() + "; "
-                + "isStagedSessionApplied = " + session.isStagedSessionApplied() + "; "
-                + "isStagedSessionFailed = " + session.isStagedSessionFailed() + "; "
-                + "stagedSessionErrorMessage = " + session.getStagedSessionErrorMessage() + "}";
+        return "{" + "appPackageName = " + mActual.getAppPackageName() + "; "
+                + "sessionId = " + mActual.getSessionId() + "; "
+                + "isStagedSessionReady = " + mActual.isStagedSessionReady() + "; "
+                + "isStagedSessionApplied = " + mActual.isStagedSessionApplied() + "; "
+                + "isStagedSessionFailed = " + mActual.isStagedSessionFailed() + "; "
+                + "stagedSessionErrorMessage = " + mActual.getStagedSessionErrorMessage() + "}";
     }
 }
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/SessionUpdateBroadcastReceiver.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/SessionUpdateBroadcastReceiver.java
deleted file mode 100644
index d51c091..0000000
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/SessionUpdateBroadcastReceiver.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests.stagedinstall;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInstaller;
-import android.util.Log;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-public class SessionUpdateBroadcastReceiver extends BroadcastReceiver {
-
-    static final BlockingQueue<PackageInstaller.SessionInfo> sessionBroadcasts
-            = new LinkedBlockingQueue<>();
-    static final BlockingQueue<PackageInstaller.SessionInfo> sessionCommittedBroadcasts
-            = new LinkedBlockingQueue<>();
-
-    private static final String TAG = "StagedInstallTest";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        PackageInstaller.SessionInfo info =
-                intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
-        assertThat(info).isNotNull();
-        switch (intent.getAction()) {
-            case PackageInstaller.ACTION_SESSION_UPDATED:
-                handleSessionUpdatedBroadcast(info);
-                break;
-            case PackageInstaller.ACTION_SESSION_COMMITTED:
-                handleSessionCommittedBroadcast(info);
-                break;
-            default:
-                break;
-        }
-    }
-
-    private void handleSessionUpdatedBroadcast(PackageInstaller.SessionInfo info) {
-        Log.i(TAG, "Received SESSION_UPDATED for session " + info.getSessionId()
-                + " isReady:" + info.isStagedSessionReady()
-                + " isFailed:" + info.isStagedSessionFailed()
-                + " isApplied:" + info.isStagedSessionApplied());
-        try {
-            sessionBroadcasts.put(info);
-        } catch (InterruptedException e) {
-
-        }
-    }
-
-    private void handleSessionCommittedBroadcast(PackageInstaller.SessionInfo info) {
-        Log.e(TAG, "Received SESSION_COMMITTED for session " + info.getSessionId());
-        try {
-            sessionCommittedBroadcasts.put(info);
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new IllegalStateException(
-                    "Interrupted while handling SESSION_COMMITTED broadcast", e);
-        }
-    }
-}
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
index 8615e4b..a218257 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
@@ -16,6 +16,8 @@
 
 package com.android.tests.stagedinstall;
 
+import static com.android.cts.install.lib.InstallUtils.assertStatusSuccess;
+import static com.android.cts.install.lib.InstallUtils.getInstalledVersion;
 import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
 import static com.android.cts.shim.lib.ShimPackage.DIFFERENT_APEX_PACKAGE_NAME;
 import static com.android.cts.shim.lib.ShimPackage.NOT_PRE_INSTALL_APEX_PACKAGE_NAME;
@@ -29,15 +31,16 @@
 import static org.junit.Assert.fail;
 
 import android.Manifest;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.storage.StorageManager;
 import android.util.Log;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -61,18 +64,19 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -177,12 +181,6 @@
                 .dropShellPermissionIdentity();
     }
 
-    @Before
-    public void clearBroadcastReceiver() {
-        SessionUpdateBroadcastReceiver.sessionBroadcasts.clear();
-        SessionUpdateBroadcastReceiver.sessionCommittedBroadcasts.clear();
-    }
-
     // This is marked as @Test to take advantage of @Before/@After methods of this class. Actual
     // purpose of this method to be called before and after each test case of
     // com.android.test.stagedinstall.host.StagedInstallTest to reduce tests flakiness.
@@ -224,21 +222,23 @@
 
     @Test
     public void testInstallStagedApk_Commit() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         waitForIsReadyBroadcast(sessionId);
         assertSessionReady(sessionId);
         storeSessionId(sessionId);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
     public void testInstallStagedApk_VerifyPostReboot() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
@@ -253,6 +253,7 @@
 
     @Test
     public void testInstallMultipleStagedApks_Commit() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = stageMultipleApks(TestApp.A1, TestApp.B1)
                 .assertSuccessful().getSessionId();
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
@@ -262,16 +263,17 @@
         storeSessionId(sessionId);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
     public void testInstallMultipleStagedApks_VerifyPostReboot() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
@@ -279,10 +281,10 @@
         int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         waitForIsReadyBroadcast(sessionId);
-        PackageInstaller.SessionInfo session = getStagedSessionInfo(sessionId);
+        PackageInstaller.SessionInfo session = InstallUtils.getStagedSessionInfo(sessionId);
         assertSessionReady(sessionId);
         abandonSession(sessionId);
-        assertThat(getStagedSessionInfo(sessionId)).isNull();
+        InstallUtils.assertStagedSessionIsAbandoned(sessionId);
         // Allow the session to be removed from PackageInstaller
         Duration spentWaiting = Duration.ZERO;
         while (spentWaiting.compareTo(WAIT_FOR_SESSION_REMOVED_TTL) < 0) {
@@ -312,7 +314,7 @@
         int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         abandonSession(sessionId);
-        assertThat(getStagedSessionInfo(sessionId)).isNull();
+        InstallUtils.assertStagedSessionIsAbandoned(sessionId);
     }
 
     @Test
@@ -339,6 +341,7 @@
 
     @Test
     public void testNoSessionUpdatedBroadcastSentForStagedSessionAbandon() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_UPDATED);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         // Using an apex in hopes that pre-reboot verification will take longer to complete
@@ -346,8 +349,8 @@
         int sessionId = stageMultipleApks(TestApp.A1, TestApp.Apex2).assertSuccessful()
                 .getSessionId();
         abandonSession(sessionId);
-        assertThat(getStagedSessionInfo(sessionId)).isNull();
-        assertNoSessionUpdatedBroadcastSent();
+        InstallUtils.assertStagedSessionIsAbandoned(sessionId);
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
@@ -456,6 +459,7 @@
 
     @Test
     public void testInstallStagedApex_Commit() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
         waitForIsReadyBroadcast(sessionId);
@@ -463,19 +467,21 @@
         storeSessionId(sessionId);
         // Version shouldn't change before reboot.
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
     public void testInstallStagedApex_VerifyPostReboot() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
     public void testInstallStagedApexAndApk_Commit() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         int sessionId = stageMultipleApks(TestApp.Apex2, TestApp.A1)
@@ -486,16 +492,17 @@
         // Version shouldn't change before reboot.
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
     public void testInstallStagedApexAndApk_VerifyPostReboot() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
@@ -835,7 +842,7 @@
 
     @Test
     public void testUpdateWithDifferentKey_VerifyPostReboot() throws Exception {
-        assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
+        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
     }
 
     // Once updated with a new rotated key (bob), further updates with old key (alice) should fail
@@ -863,7 +870,7 @@
 
     @Test
     public void testTrustedOldKeyIsAccepted_VerifyPostReboot() throws Exception {
-        assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
+        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
     }
 
     // Once updated with a new rotated key (bob), further updates with new key (bob) should pass
@@ -876,7 +883,7 @@
 
     @Test
     public void testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot() throws Exception {
-        assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
+        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
     }
 
     // Once updated with a new rotated key (bob), further updates can be done with key only
@@ -896,16 +903,20 @@
     @Test
     public void testFailStagingMultipleSessionsIfNoCheckPoint() throws Exception {
         stageSingleApk(TestApp.A1).assertSuccessful();
-        StageSessionResult failedSessionResult = stageSingleApk(TestApp.B1);
-        assertThat(failedSessionResult.getErrorMessage()).contains(
+        int sessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId();
+        PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
+        assertThat(info).isStagedSessionFailed();
+        assertThat(info.getStagedSessionErrorMessage()).contains(
                 "Cannot stage multiple sessions without checkpoint support");
     }
 
     @Test
     public void testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk() throws Exception {
         stageSingleApk(TestApp.A1).assertSuccessful();
-        StageSessionResult failedSessionResult = stageSingleApk(TestApp.A1);
-        assertThat(failedSessionResult.getErrorMessage()).contains(
+        int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+        PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
+        assertThat(info).isStagedSessionFailed();
+        assertThat(info.getStagedSessionErrorMessage()).contains(
                 "has been staged already by session");
     }
 
@@ -919,8 +930,10 @@
     @Test
     public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception {
         stageMultipleApks(TestApp.A1, TestApp.B1).assertSuccessful();
-        StageSessionResult failedSessionResult = stageMultipleApks(TestApp.A2, TestApp.C1);
-        assertThat(failedSessionResult.getErrorMessage()).contains(
+        int sessionId = stageMultipleApks(TestApp.A2, TestApp.C1).assertSuccessful().getSessionId();
+        PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
+        assertThat(info).isStagedSessionFailed();
+        assertThat(info.getStagedSessionErrorMessage()).contains(
                 "has been staged already by session");
     }
 
@@ -1174,26 +1187,17 @@
                 .contains("AVB footer verification failed");
     }
 
-    private static long getInstalledVersion(String packageName) {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        PackageManager pm = context.getPackageManager();
-        try {
-            PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
-            return info.getLongVersionCode();
-        } catch (PackageManager.NameNotFoundException e) {
-            return -1;
-        }
-    }
-
     // It becomes harder to maintain this variety of install-related helper methods.
     // TODO(ioffe): refactor install-related helper methods into a separate utility.
     private static int createStagedSession() throws Exception {
         return Install.single(TestApp.A1).setStaged().createSession();
     }
 
-    private static void commitSession(int sessionId) throws IOException {
+    private static Intent commitSession(int sessionId) throws IOException, InterruptedException {
+        LocalIntentSender sender = new LocalIntentSender();
         InstallUtils.openPackageInstallerSession(sessionId)
-                .commit(LocalIntentSender.getIntentSender());
+                .commit(sender.getIntentSender());
+        return sender.getResult();
     }
 
     private static StageSessionResult stageDowngradeSingleApk(TestApp testApp) throws Exception {
@@ -1201,26 +1205,20 @@
         int sessionId = Install.single(testApp).setStaged().setRequestDowngrade().createSession();
         // Commit the session (this will start the installation workflow).
         Log.i(TAG, "Committing downgrade session for apk: " + testApp);
-        commitSession(sessionId);
-        return new StageSessionResult(sessionId, LocalIntentSender.getIntentSenderResult());
+        Intent result = commitSession(sessionId);
+        return new StageSessionResult(sessionId, result);
     }
 
     private static StageSessionResult stageSingleApk(String apkFileName, String outputFileName)
             throws Exception {
-        Log.i(TAG, "Staging an install of " + apkFileName);
-        // this is a trick to open an empty install session so we can manually write the package
-        // using writeApk
-        TestApp empty = new TestApp(null, null, -1,
-                apkFileName.endsWith(".apex"));
-        int sessionId = Install.single(empty).setStaged().createSession();
-        try (PackageInstaller.Session session =
-                     InstallUtils.openPackageInstallerSession(sessionId)) {
-            writeApk(session, apkFileName, outputFileName);
-            // Commit the session (this will start the installation workflow).
-            Log.i(TAG, "Committing session for apk: " + apkFileName);
-            commitSession(sessionId);
-            return new StageSessionResult(sessionId, LocalIntentSender.getIntentSenderResult());
+        File tmpFile = File.createTempFile(outputFileName, null);
+        try (InputStream is =
+                     StagedInstallTest.class.getClassLoader().getResourceAsStream(apkFileName)) {
+            Files.copy(is, tmpFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
         }
+        TestApp testApp = new TestApp(tmpFile.getName(), null, -1,
+                apkFileName.endsWith(".apex"), tmpFile);
+        return stageSingleApk(testApp);
     }
 
     private static StageSessionResult stageSingleApk(TestApp testApp) throws Exception {
@@ -1228,17 +1226,15 @@
         int sessionId = Install.single(testApp).setStaged().createSession();
         // Commit the session (this will start the installation workflow).
         Log.i(TAG, "Committing session for apk: " + testApp);
-        commitSession(sessionId);
-        return new StageSessionResult(sessionId,
-                LocalIntentSender.getIntentSenderResult(sessionId));
+        Intent result = commitSession(sessionId);
+        return new StageSessionResult(sessionId, result);
     }
 
     private static StageSessionResult stageMultipleApks(TestApp... testApps) throws Exception {
         Log.i(TAG, "Staging an install of " + Arrays.toString(testApps));
         int multiPackageSessionId = Install.multi(testApps).setStaged().createSession();
-        commitSession(multiPackageSessionId);
-        return new StageSessionResult(
-                multiPackageSessionId, LocalIntentSender.getIntentSenderResult());
+        Intent result = commitSession(multiPackageSessionId);
+        return new StageSessionResult(multiPackageSessionId, result);
     }
 
     private static void assertSessionApplied(int sessionId) {
@@ -1313,20 +1309,6 @@
         }
     }
 
-    private static void writeApk(PackageInstaller.Session session, String apkFileName,
-            String outputFileName)
-            throws Exception {
-        try (OutputStream packageInSession = session.openWrite(outputFileName, 0, -1);
-             InputStream is =
-                     StagedInstallTest.class.getClassLoader().getResourceAsStream(apkFileName)) {
-            byte[] buffer = new byte[4096];
-            int n;
-            while ((n = is.read(buffer)) >= 0) {
-                packageInSession.write(buffer, 0, n);
-            }
-        }
-    }
-
     // TODO(ioffe): not really-tailored to staged install, rename to InstallResult?
     private static final class StageSessionResult {
         private final int sessionId;
@@ -1351,6 +1333,38 @@
         }
     }
 
+    /**
+     * Counts the number of broadcast intents received for a given type during the test.
+     * Used by to check no broadcast intents were received during the test.
+     */
+    private static class BroadcastCounter extends BroadcastReceiver {
+        private final Context mContext;
+        private final AtomicInteger mNumBroadcastReceived = new AtomicInteger();
+
+        BroadcastCounter(String action) {
+            mContext = InstrumentationRegistry.getInstrumentation().getContext();
+            mContext.registerReceiver(this, new IntentFilter(action));
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mNumBroadcastReceived.incrementAndGet();
+        }
+
+        /**
+         * Waits for a while and checks no broadcasts are received.
+         */
+        void assertNoBroadcastReceived() {
+            try {
+                // Sleep for a reasonable amount of time and check no broadcast is received
+                Thread.sleep(TimeUnit.SECONDS.toMillis(10));
+            } catch (InterruptedException ignore) {
+            }
+            mContext.unregisterReceiver(this);
+            assertThat(mNumBroadcastReceived.get()).isEqualTo(0);
+        }
+    }
+
     private static String extractErrorMessage(Intent result) {
         int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                 PackageInstaller.STATUS_FAILURE);
@@ -1367,82 +1381,23 @@
         getPackageInstaller().abandonSession(sessionId);
     }
 
-    /**
-     * Returns the session by session Id, or null if no session is found.
-     */
-    private static PackageInstaller.SessionInfo getStagedSessionInfo(int sessionId) {
-        PackageInstaller packageInstaller = getPackageInstaller();
-        for (PackageInstaller.SessionInfo session : packageInstaller.getStagedSessions()) {
-            if (session.getSessionId() == sessionId) {
-                return session;
-            }
-        }
-        return null;
-    }
-
     private static PackageInstaller.SessionInfo getSessionInfo(int sessionId) {
         return getPackageInstaller().getSessionInfo(sessionId);
     }
 
-    private static void assertStatusSuccess(Intent result) {
-        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status == -1) {
-            throw new AssertionError("PENDING USER ACTION");
-        } else if (status > 0) {
-            String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-            throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
-        }
-    }
-
     private void waitForIsFailedBroadcast(int sessionId) {
         Log.i(TAG, "Waiting for session " + sessionId + " to be marked as failed");
-        try {
-
-            PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
-            assertThat(info).isStagedSessionFailed();
-        } catch (Exception e) {
-            throw new AssertionError(e);
-        }
+        PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
+        assertThat(info).isStagedSessionFailed();
     }
 
     private void waitForIsReadyBroadcast(int sessionId) {
         Log.i(TAG, "Waiting for session " + sessionId + " to be ready");
-        try {
-            PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
-            assertThat(info).isStagedSessionReady();
-        } catch (Exception e) {
-            throw new AssertionError(e);
-        }
+        PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
+        assertThat(info).isStagedSessionReady();
     }
 
-    private PackageInstaller.SessionInfo waitForBroadcast(int sessionId) throws Exception {
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertWithMessage("Timed out while waiting for session to get ready")
-                .that(info).isNotNull();
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
-        return info;
-    }
-
-    private void assertNoSessionCommitBroadcastSent() throws Exception {
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionCommittedBroadcasts.poll(10,
-                        TimeUnit.SECONDS);
-        assertThat(info).isNull();
-    }
-
-    private void assertNoSessionUpdatedBroadcastSent() throws Exception {
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(10,
-                        TimeUnit.SECONDS);
-        assertThat(info).isNull();
-    }
-
-    @Test
-    public void isCheckpointSupported() {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
-        assertThat(sm.isCheckpointSupported()).isTrue();
+    private PackageInstaller.SessionInfo waitForBroadcast(int sessionId) {
+        return InstallUtils.waitForSession(sessionId);
     }
 }
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java
index 36a67e1..dba49e9 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java
@@ -23,11 +23,11 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
 
+import android.cts.install.lib.host.InstallUtilsHost;
 import android.platform.test.annotations.LargeTest;
 
-import com.android.cts.install.lib.host.InstallUtilsHost;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.AaptParser;
@@ -37,7 +37,6 @@
 import com.android.tradefed.util.RunUtil;
 import com.android.tradefed.util.ZipUtil;
 
-import org.hamcrest.CoreMatchers;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -110,8 +109,7 @@
 
     @Before
     public void setUp() throws Exception {
-        assumeThat("Device doesn't support updating APEX", mHostUtils.isApexUpdateSupported(),
-                CoreMatchers.equalTo("true"));
+        assumeTrue("Device doesn't support updating APEX", mHostUtils.isApexUpdateSupported());
         cleanUp();
         mDeapexerZip = getTestInformation().getDependencyFile(DEAPEXER_ZIP_FILE_NAME, false);
         mAllApexesZip = getTestInformation().getDependencyFile(STAGED_INSTALL_TEST_FILE_NAME,
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index ad1bdd5..7efeb6b 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -27,9 +27,9 @@
 import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
 
+import android.cts.install.lib.host.InstallUtilsHost;
 import android.platform.test.annotations.LargeTest;
 
-import com.android.cts.install.lib.host.InstallUtilsHost;
 import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
@@ -87,14 +87,14 @@
     @Before
     public void setUp() throws Exception {
         cleanUp();
-        uninstallShimApexIfNecessary();
+        mHostUtils.uninstallShimApexIfNecessary();
         storeDefaultLauncher();
     }
 
     @After
     public void tearDown() throws Exception {
         cleanUp();
-        uninstallShimApexIfNecessary();
+        mHostUtils.uninstallShimApexIfNecessary();
         setDefaultLauncher(mDefaultLauncher);
     }
 
@@ -171,7 +171,9 @@
 
     @Test
     public void testGetActiveStagedSessions() throws Exception {
-        assumeTrue(isCheckpointSupported());
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
         runPhase("testGetActiveStagedSessions");
     }
 
@@ -191,7 +193,9 @@
 
     @Test
     public void testGetActiveStagedSessions_MultiApkSession() throws Exception {
-        assumeTrue(isCheckpointSupported());
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
         runPhase("testGetActiveStagedSessions_MultiApkSession");
     }
 
@@ -222,7 +226,9 @@
     public void testShimApexShouldPreInstalledIfUpdatingApexIsSupported() throws Exception {
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
-        final ITestDevice.ApexInfo shimApex = getShimApex();
+        final ITestDevice.ApexInfo shimApex = mHostUtils.getShimApex().orElseThrow(
+                () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME)
+        );
         assertThat(shimApex.versionCode).isEqualTo(1);
     }
 
@@ -492,7 +498,9 @@
     // Should fail to stage multiple sessions when check-point is not available
     @Test
     public void testFailStagingMultipleSessionsIfNoCheckPoint() throws Exception {
-        assumeFalse(isCheckpointSupported());
+        assumeFalse("Device supports file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
         runPhase("testFailStagingMultipleSessionsIfNoCheckPoint");
     }
 
@@ -504,13 +512,17 @@
     @Test
     public void testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()
             throws Exception {
-        assumeTrue(isCheckpointSupported());
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
         runPhase("testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk");
     }
 
     @Test
     public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception {
-        assumeTrue(isCheckpointSupported());
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
         runPhase("testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk");
     }
 
@@ -518,7 +530,9 @@
     @Test
     @LargeTest
     public void testMultipleStagedInstall_ApkOnly() throws Exception {
-        assumeTrue(isCheckpointSupported());
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
         runPhase("testMultipleStagedInstall_ApkOnly_Commit");
         getDevice().reboot();
         runPhase("testMultipleStagedInstall_ApkOnly_VerifyPostReboot");
@@ -528,7 +542,9 @@
     @Test
     @LargeTest
     public void testInstallMultipleStagedSession_PartialFail_ApkOnly() throws Exception {
-        assumeTrue(isCheckpointSupported());
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
         runPhase("testInstallMultipleStagedSession_PartialFail_ApkOnly_Commit");
         getDevice().reboot();
         runPhase("testInstallMultipleStagedSession_PartialFail_ApkOnly_VerifyPostReboot");
@@ -538,7 +554,9 @@
     @Test
     @LargeTest
     public void testFailureReasonPersists_SingleSession() throws Exception {
-        assumeTrue(isCheckpointSupported());
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
         runPhase("testFailureReasonPersists_SingleSession_Commit");
         getDevice().reboot();
         runPhase("testFailureReasonPersists_SingleSession_VerifyPostReboot");
@@ -548,7 +566,9 @@
     @Test
     @LargeTest
     public void testFailureReasonPersists_MultiSession() throws Exception {
-        assumeTrue(isCheckpointSupported());
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
         runPhase("testFailureReasonPersists_MultipleSession_Commit");
         getDevice().reboot();
         runPhase("testFailureReasonPersists_MultipleSession_VerifyPostReboot");
@@ -567,8 +587,6 @@
     @Test
     @LargeTest
     public void testInstallApkChangingFingerprint() throws Exception {
-        assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
-
         try {
             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade true");
             runPhase("testInstallApkChangingFingerprint");
@@ -640,42 +658,6 @@
     }
 
     /**
-     * Uninstalls a shim apex only if it's latest version is installed on /data partition (i.e.
-     * it has a version higher than {@code 1}).
-     *
-     * <p>This is purely to optimize tests run time. Since uninstalling an apex requires a reboot,
-     * and only a small subset of tests successfully install an apex, this code avoids ~10
-     * unnecessary reboots.
-     */
-    private void uninstallShimApexIfNecessary() throws Exception {
-        if (!mHostUtils.isApexUpdateSupported()) {
-            // Device doesn't support updating apex. Nothing to uninstall.
-            return;
-        }
-        if (getShimApex().sourceDir.startsWith("/system")) {
-            // System version is active, nothing to uninstall.
-            return;
-        }
-        // Non system version is active, need to uninstall it and reboot the device.
-        Log.i(TAG, "Uninstalling shim apex");
-        final String errorMessage = getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
-        if (errorMessage != null) {
-            Log.e(TAG, "Failed to uninstall " + SHIM_APEX_PACKAGE_NAME + " : " + errorMessage);
-        } else {
-            getDevice().reboot();
-            final ITestDevice.ApexInfo shim = getShimApex();
-            assertThat(shim.versionCode).isEqualTo(1L);
-            assertThat(shim.sourceDir).startsWith("/system");
-        }
-    }
-
-    private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException {
-        return getDevice().getActiveApexes().stream().filter(
-                apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME)).findAny().orElseThrow(
-                () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME));
-    }
-
-    /**
      * Store the component name of the default launcher. This value will be used to reset the
      * default launcher to its correct component upon test completion.
      */
@@ -734,15 +716,6 @@
         }
     }
 
-    private boolean isCheckpointSupported() throws Exception {
-        try {
-            runPhase("isCheckpointSupported");
-            return true;
-        } catch (AssertionError ignore) {
-            return false;
-        }
-    }
-
     private boolean isDebuggable() throws Exception {
         return getDevice().getIntProperty("ro.debuggable", 0) == 1;
     }
diff --git a/hostsidetests/stagedinstall/testdata/apk/StagedInstallTestAppSamePackageNameAsApex.xml b/hostsidetests/stagedinstall/testdata/apk/StagedInstallTestAppSamePackageNameAsApex.xml
index 2954538..6aa9b5b 100644
--- a/hostsidetests/stagedinstall/testdata/apk/StagedInstallTestAppSamePackageNameAsApex.xml
+++ b/hostsidetests/stagedinstall/testdata/apk/StagedInstallTestAppSamePackageNameAsApex.xml
@@ -22,7 +22,8 @@
     <uses-sdk android:minSdkVersion="19" />
 
     <application android:label="StagedInstall Test App With Same Package Name As Apex">
-        <activity android:name="com.android.tests.stagedinstall.testapp.MainActivity">
+        <activity android:name="com.android.tests.stagedinstall.testapp.MainActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/hostsidetests/statsd/TEST_MAPPING b/hostsidetests/statsd/TEST_MAPPING
new file mode 100644
index 0000000..f48ff4b
--- /dev/null
+++ b/hostsidetests/statsd/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit" : [
+    {
+      "name" : "CtsStatsdHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
index 2b479bd..875488b 100644
--- a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
+++ b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
@@ -15,9 +15,9 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.server.cts.device.statsd"
-          android:versionCode="10" >
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+     package="com.android.server.cts.device.statsd"
+     android:versionCode="10">
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
@@ -27,86 +27,91 @@
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
-    <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS" />
-    <uses-permission android:name="android.permission.DUMP" /> <!-- must be granted via pm grant -->
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.READ_SYNC_STATS" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-    <uses-permission android:name="android.permission.VIBRATE" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+    <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"/>
+    <uses-permission android:name="android.permission.DUMP"/> <!-- must be granted via pm grant -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.READ_SYNC_STATS"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
     <application android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
 
-        <service android:name=".StatsdCtsBackgroundService" android:exported="true" />
-        <activity android:name=".StatsdCtsForegroundActivity" android:exported="true" />
+        <service android:name=".StatsdCtsBackgroundService"
+             android:exported="true"/>
+        <activity android:name=".StatsdCtsForegroundActivity"
+             android:exported="true"/>
         <service android:name=".StatsdCtsForegroundService"
-                 android:foregroundServiceType="camera" android:exported="true" />
+             android:foregroundServiceType="camera"
+             android:exported="true"/>
 
-        <activity
-            android:name=".VideoPlayerActivity"
-            android:label="@string/app_name"
-            android:resizeableActivity="true"
-            android:supportsPictureInPicture="true"
-            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
-            android:launchMode="singleTop" >
+        <activity android:name=".VideoPlayerActivity"
+             android:label="@string/app_name"
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="true"
+             android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+             android:launchMode="singleTop"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name=".DaveyActivity" android:exported="true" />
-        <activity android:name=".HiddenApiUsedActivity" android:exported="true" />
-        <activity
-            android:name=".ANRActivity"
-            android:label="ANR Test Activity"
-            android:launchMode="singleInstance"
-            android:process=":ANRProcess"
-            android:exported="true"
-          />
+        <activity android:name=".DaveyActivity"
+             android:exported="true"/>
+        <activity android:name=".ANRActivity"
+             android:label="ANR Test Activity"
+             android:launchMode="singleInstance"
+             android:process=":ANRProcess"
+             android:exported="true"/>
 
         <service android:name=".StatsdAuthenticator"
-            android:exported="false">
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
 
             <meta-data android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/authenticator" />
+                 android:resource="@xml/authenticator"/>
         </service>
         <service android:name="StatsdSyncService"
-            android:exported="false" >
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.content.SyncAdapter" />
+                <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
             <meta-data android:name="android.content.SyncAdapter"
-                android:resource="@xml/syncadapter" />
+                 android:resource="@xml/syncadapter"/>
         </service>
 
         <provider android:name=".StatsdProvider"
-            android:authorities="com.android.server.cts.device.statsd.provider" />
+             android:authorities="com.android.server.cts.device.statsd.provider"/>
 
         <service android:name=".StatsdJobService"
-            android:permission="android.permission.BIND_JOB_SERVICE" />
+             android:permission="android.permission.BIND_JOB_SERVICE"/>
 
         <service android:name=".DummyCallscreeningService"
-                 android:permission="android.permission.BIND_SCREENING_SERVICE">
+             android:permission="android.permission.BIND_SCREENING_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.CallScreeningService" />
+                <action android:name="android.telecom.CallScreeningService"/>
             </intent-filter>
         </service>
 
-        <service android:name=".IsolatedProcessService" android:isolatedProcess="true" />
+        <service android:name=".IsolatedProcessService"
+             android:isolatedProcess="true"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.server.cts.device.statsd"
-                     android:label="CTS tests of android.os.statsd stats collection">
+         android:targetPackage="com.android.server.cts.device.statsd"
+         android:label="CTS tests of android.os.statsd stats collection">
         <meta-data android:name="listener"
-                   android:value="com.android.cts.runner.CtsTestRunListener" />
-    </instrumentation>/>
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
 </manifest>
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
index e4d473b..b967c5f 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
@@ -17,7 +17,6 @@
 package com.android.server.cts.device.statsd;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.accounts.Account;
@@ -68,22 +67,17 @@
 import android.os.SystemClock;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.StatsEvent;
 import android.util.StatsLog;
-
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
-
 import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.utils.blob.DummyBlobData;
-
+import com.android.utils.blob.FakeBlobData;
 import com.google.common.io.BaseEncoding;
-
-import org.junit.Test;
-
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.util.Arrays;
@@ -93,6 +87,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
+import org.junit.Test;
 
 public class AtomTests {
     private static final String TAG = AtomTests.class.getSimpleName();
@@ -258,23 +253,38 @@
             int uid = Process.myUid();
             int whatAtomId = 9_999;
 
+            // Get the current setting for bluetooth background scanning.
+            // Set to 0 if the setting is not found or an error occurs.
+            int initialBleScanGlobalSetting = Settings.Global.getInt(
+                    InstrumentationRegistry.getTargetContext().getContentResolver(),
+                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
+
+            // Turn off bluetooth background scanning.
+            Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
+                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
+
             // Change state to State.ON.
             bleScanner.startScan(null, scanSettings, scanCallback);
-            sleep(500);
+            sleep(6_000);
             writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
             writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+
             bluetoothAdapter.disable();
-            sleep(1500);
+            sleep(6_000);
 
             // Trigger State.RESET so that new state is State.OFF.
             if (!bluetoothAdapter.enable()) {
                 Log.e(TAG, "Could not enable bluetooth to trigger state reset");
                 return;
             }
-            sleep(3_000); // Wait for Bluetooth to fully turn on.
+            sleep(6_000); // Wait for Bluetooth to fully turn on.
             writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
             writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
             writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+
+            // Set bluetooth background scanning to original setting.
+            Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
+                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, initialBleScanGlobalSetting);
         });
     }
 
@@ -934,20 +944,6 @@
         }
     }
 
-    @Test
-    public void testIsolatedProcessService() throws Exception {
-        Context context = InstrumentationRegistry.getContext();
-        int uid = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0).uid;
-
-        // Start the isolated service, which logs an AppBreadcrumbReported atom, and then exit
-        // shortly afterwards.
-        Intent intent = new Intent(context, IsolatedProcessService.class);
-        context.startService(intent);
-        sleep(500);
-        context.stopService(intent);
-    }
-
-
     // Constants for testBlobStore
     private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
     private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
@@ -964,7 +960,7 @@
         BlobStoreManager bsm = context.getSystemService(BlobStoreManager.class);
         final long leaseExpiryMs = System.currentTimeMillis() + BLOB_LEASE_EXPIRY_DURATION_MS;
 
-        final DummyBlobData blobData = new DummyBlobData.Builder(context).setExpiryDurationMs(
+        final FakeBlobData blobData = new FakeBlobData.Builder(context).setExpiryDurationMs(
                 BLOB_EXPIRY_DURATION_MS).setFileSize(BLOB_FILE_SIZE_BYTES).build();
 
         blobData.prepare();
@@ -1041,7 +1037,7 @@
     }
 
 
-    private void commitBlob(Context context, BlobStoreManager bsm, DummyBlobData blobData)
+    private void commitBlob(Context context, BlobStoreManager bsm, FakeBlobData blobData)
             throws Exception {;
         final long sessionId = bsm.createSession(blobData.getBlobHandle());
         try (BlobStoreManager.Session session = bsm.openSession(sessionId)) {
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/HiddenApiUsedActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/HiddenApiUsedActivity.java
deleted file mode 100644
index 2132f3c..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/HiddenApiUsedActivity.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import java.lang.reflect.Field;
-
-
-public class HiddenApiUsedActivity extends Activity {
-    /** Called when the activity is first created. */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        try {
-            Field field = Activity.class.getDeclaredField("mWindow");
-            field.setAccessible(true);
-            Object object = field.get(this);
-        } catch(NoSuchFieldException e) {
-        } catch(IllegalAccessException e) {
-        }
-        finish();
-    }
-
-}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/IsolatedProcessService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/IsolatedProcessService.java
deleted file mode 100644
index 086a3be..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/IsolatedProcessService.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-import android.util.StatsLog;
-
-public class IsolatedProcessService extends Service {
-    private static final String TAG = "IsolatedProcessService";
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        StatsLog.logStart(/*label=*/0);
-        return START_NOT_STICKY;
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
index 035160f..a3f35bd 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
@@ -320,4 +320,9 @@
                 "settings get global netstats_combine_subtype_enabled").trim();
         return output.equals("1");
     }
+
+    void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
+        getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
+                + (enable ? "1" : "0"));
+    }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
deleted file mode 100644
index ee6f324..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
+++ /dev/null
@@ -1,699 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.atom;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.BatteryPluggedStateEnum;
-import android.os.BatteryStatusEnum;
-import android.platform.test.annotations.RestrictedBuildTest;
-import android.server.DeviceIdleModeEnum;
-import android.view.DisplayStateEnum;
-import android.telephony.NetworkTypeEnum;
-
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.BatterySaverModeStateChanged;
-import com.android.os.AtomsProto.BuildInformation;
-import com.android.os.AtomsProto.ConnectivityStateChanged;
-import com.android.os.AtomsProto.SimSlotState;
-import com.android.os.AtomsProto.SupportedRadioAccessFamily;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.EventMetricData;
-
-import com.google.common.collect.Range;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Statsd atom tests that are done via adb (hostside).
- */
-public class HostAtomTests extends AtomTestCase {
-
-    private static final String TAG = "Statsd.HostAtomTests";
-
-    // Either file must exist to read kernel wake lock stats.
-    private static final String WAKE_LOCK_FILE = "/proc/wakelocks";
-    private static final String WAKE_SOURCES_FILE = "/d/wakeup_sources";
-
-    // Bitmask of radio access technologies that all GSM phones should at least partially support
-    protected static final long NETWORK_TYPE_BITMASK_GSM_ALL =
-            (1 << (NetworkTypeEnum.NETWORK_TYPE_GSM_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_GPRS_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_EDGE_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_UMTS_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSDPA_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSUPA_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSPA_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSPAP_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_TD_SCDMA_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_LTE_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_LTE_CA_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_NR_VALUE - 1));
-    // Bitmask of radio access technologies that all CDMA phones should at least partially support
-    protected static final long NETWORK_TYPE_BITMASK_CDMA_ALL =
-            (1 << (NetworkTypeEnum.NETWORK_TYPE_CDMA_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_1XRTT_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_EVDO_0_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_EVDO_A_VALUE - 1))
-            | (1 << (NetworkTypeEnum.NETWORK_TYPE_EHRPD_VALUE - 1));
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    public void testScreenStateChangedAtom() throws Exception {
-        // Setup, make sure the screen is off and turn off AoD if it is on.
-        // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
-        // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
-        String aodState = getAodState();
-        setAodState("0");
-        turnScreenOn();
-        Thread.sleep(WAIT_TIME_SHORT);
-        turnScreenOff();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        final int atomTag = Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER;
-
-        Set<Integer> screenOnStates = new HashSet<>(
-                Arrays.asList(DisplayStateEnum.DISPLAY_STATE_ON_VALUE,
-                        DisplayStateEnum.DISPLAY_STATE_ON_SUSPEND_VALUE,
-                        DisplayStateEnum.DISPLAY_STATE_VR_VALUE));
-        Set<Integer> screenOffStates = new HashSet<>(
-                Arrays.asList(DisplayStateEnum.DISPLAY_STATE_OFF_VALUE,
-                        DisplayStateEnum.DISPLAY_STATE_DOZE_VALUE,
-                        DisplayStateEnum.DISPLAY_STATE_DOZE_SUSPEND_VALUE,
-                        DisplayStateEnum.DISPLAY_STATE_UNKNOWN_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(screenOnStates, screenOffStates);
-
-        createAndUploadConfig(atomTag);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Trigger events in same order.
-        turnScreenOn();
-        Thread.sleep(WAIT_TIME_LONG);
-        turnScreenOff();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-        // reset screen to on
-        turnScreenOn();
-        // Restores AoD to initial state.
-        setAodState(aodState);
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
-                atom -> atom.getScreenStateChanged().getState().getNumber());
-    }
-
-    public void testChargingStateChangedAtom() throws Exception {
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
-        // Setup, set charging state to full.
-        setChargingState(5);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        final int atomTag = Atom.CHARGING_STATE_CHANGED_FIELD_NUMBER;
-
-        Set<Integer> batteryUnknownStates = new HashSet<>(
-                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_UNKNOWN_VALUE));
-        Set<Integer> batteryChargingStates = new HashSet<>(
-                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_CHARGING_VALUE));
-        Set<Integer> batteryDischargingStates = new HashSet<>(
-                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_DISCHARGING_VALUE));
-        Set<Integer> batteryNotChargingStates = new HashSet<>(
-                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_NOT_CHARGING_VALUE));
-        Set<Integer> batteryFullStates = new HashSet<>(
-                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_FULL_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(batteryUnknownStates, batteryChargingStates,
-                batteryDischargingStates, batteryNotChargingStates, batteryFullStates);
-
-        createAndUploadConfig(atomTag);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Trigger events in same order.
-        setChargingState(1);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setChargingState(2);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setChargingState(3);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setChargingState(4);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setChargingState(5);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Unfreeze battery state after test
-        resetBatteryStatus();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-                atom -> atom.getChargingStateChanged().getState().getNumber());
-    }
-
-    public void testPluggedStateChangedAtom() throws Exception {
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
-        // Setup, unplug device.
-        unplugDevice();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        final int atomTag = Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER;
-
-        Set<Integer> unpluggedStates = new HashSet<>(
-                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_NONE_VALUE));
-        Set<Integer> acStates = new HashSet<>(
-                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_AC_VALUE));
-        Set<Integer> usbStates = new HashSet<>(
-                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_USB_VALUE));
-        Set<Integer> wirelessStates = new HashSet<>(
-                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_WIRELESS_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(acStates, unpluggedStates, usbStates,
-                unpluggedStates, wirelessStates, unpluggedStates);
-
-        createAndUploadConfig(atomTag);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Trigger events in same order.
-        plugInAc();
-        Thread.sleep(WAIT_TIME_SHORT);
-        unplugDevice();
-        Thread.sleep(WAIT_TIME_SHORT);
-        plugInUsb();
-        Thread.sleep(WAIT_TIME_SHORT);
-        unplugDevice();
-        Thread.sleep(WAIT_TIME_SHORT);
-        plugInWireless();
-        Thread.sleep(WAIT_TIME_SHORT);
-        unplugDevice();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Unfreeze battery state after test
-        resetBatteryStatus();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-                atom -> atom.getPluggedStateChanged().getState().getNumber());
-    }
-
-    public void testBatteryLevelChangedAtom() throws Exception {
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
-        // Setup, set battery level to full.
-        setBatteryLevel(100);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        final int atomTag = Atom.BATTERY_LEVEL_CHANGED_FIELD_NUMBER;
-
-        Set<Integer> batteryLow = new HashSet<>(Arrays.asList(2));
-        Set<Integer> battery25p = new HashSet<>(Arrays.asList(25));
-        Set<Integer> battery50p = new HashSet<>(Arrays.asList(50));
-        Set<Integer> battery75p = new HashSet<>(Arrays.asList(75));
-        Set<Integer> batteryFull = new HashSet<>(Arrays.asList(100));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(batteryLow, battery25p, battery50p,
-                battery75p, batteryFull);
-
-        createAndUploadConfig(atomTag);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Trigger events in same order.
-        setBatteryLevel(2);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setBatteryLevel(25);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setBatteryLevel(50);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setBatteryLevel(75);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setBatteryLevel(100);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Unfreeze battery state after test
-        resetBatteryStatus();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-                atom -> atom.getBatteryLevelChanged().getBatteryLevel());
-    }
-
-    public void testDeviceIdleModeStateChangedAtom() throws Exception {
-        // Setup, leave doze mode.
-        leaveDozeMode();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        final int atomTag = Atom.DEVICE_IDLE_MODE_STATE_CHANGED_FIELD_NUMBER;
-
-        Set<Integer> dozeOff = new HashSet<>(
-                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_OFF_VALUE));
-        Set<Integer> dozeLight = new HashSet<>(
-                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_LIGHT_VALUE));
-        Set<Integer> dozeDeep = new HashSet<>(
-                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_DEEP_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(dozeLight, dozeDeep, dozeOff);
-
-        createAndUploadConfig(atomTag);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Trigger events in same order.
-        enterDozeModeLight();
-        Thread.sleep(WAIT_TIME_SHORT);
-        enterDozeModeDeep();
-        Thread.sleep(WAIT_TIME_SHORT);
-        leaveDozeMode();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();;
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-                atom -> atom.getDeviceIdleModeStateChanged().getState().getNumber());
-    }
-
-    public void testBatterySaverModeStateChangedAtom() throws Exception {
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
-        // Setup, turn off battery saver.
-        turnBatterySaverOff();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        final int atomTag = Atom.BATTERY_SAVER_MODE_STATE_CHANGED_FIELD_NUMBER;
-
-        Set<Integer> batterySaverOn = new HashSet<>(
-                Arrays.asList(BatterySaverModeStateChanged.State.ON_VALUE));
-        Set<Integer> batterySaverOff = new HashSet<>(
-                Arrays.asList(BatterySaverModeStateChanged.State.OFF_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(batterySaverOn, batterySaverOff);
-
-        createAndUploadConfig(atomTag);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Trigger events in same order.
-        turnBatterySaverOn();
-        Thread.sleep(WAIT_TIME_LONG);
-        turnBatterySaverOff();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
-                atom -> atom.getBatterySaverModeStateChanged().getState().getNumber());
-    }
-
-    @RestrictedBuildTest
-    public void testRemainingBatteryCapacity() throws Exception {
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.REMAINING_BATTERY_CAPACITY_FIELD_NUMBER, null);
-
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> data = getGaugeMetricDataList();
-
-        assertThat(data).isNotEmpty();
-        Atom atom = data.get(0);
-        assertThat(atom.getRemainingBatteryCapacity().hasChargeMicroAmpereHour()).isTrue();
-        if (hasBattery()) {
-            assertThat(atom.getRemainingBatteryCapacity().getChargeMicroAmpereHour())
-                .isGreaterThan(0);
-        }
-    }
-
-    @RestrictedBuildTest
-    public void testFullBatteryCapacity() throws Exception {
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.FULL_BATTERY_CAPACITY_FIELD_NUMBER, null);
-
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> data = getGaugeMetricDataList();
-
-        assertThat(data).isNotEmpty();
-        Atom atom = data.get(0);
-        assertThat(atom.getFullBatteryCapacity().hasCapacityMicroAmpereHour()).isTrue();
-        if (hasBattery()) {
-            assertThat(atom.getFullBatteryCapacity().getCapacityMicroAmpereHour()).isGreaterThan(0);
-        }
-    }
-
-    public void testBatteryVoltage() throws Exception {
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.BATTERY_VOLTAGE_FIELD_NUMBER, null);
-
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> data = getGaugeMetricDataList();
-
-        assertThat(data).isNotEmpty();
-        Atom atom = data.get(0);
-        assertThat(atom.getBatteryVoltage().hasVoltageMillivolt()).isTrue();
-        if (hasBattery()) {
-            assertThat(atom.getBatteryVoltage().getVoltageMillivolt()).isGreaterThan(0);
-        }
-    }
-
-    // This test is for the pulled battery level atom.
-    public void testBatteryLevel() throws Exception {
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.BATTERY_LEVEL_FIELD_NUMBER, null);
-
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> data = getGaugeMetricDataList();
-
-        assertThat(data).isNotEmpty();
-        Atom atom = data.get(0);
-        assertThat(atom.getBatteryLevel().hasBatteryLevel()).isTrue();
-        if (hasBattery()) {
-            assertThat(atom.getBatteryLevel().getBatteryLevel()).isIn(Range.openClosed(0, 100));
-        }
-    }
-
-    // This test is for the pulled battery charge count atom.
-    public void testBatteryCycleCount() throws Exception {
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.BATTERY_CYCLE_COUNT_FIELD_NUMBER, null);
-
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> data = getGaugeMetricDataList();
-
-        assertThat(data).isNotEmpty();
-        Atom atom = data.get(0);
-        assertThat(atom.getBatteryCycleCount().hasCycleCount()).isTrue();
-        if (hasBattery()) {
-            assertThat(atom.getBatteryCycleCount().getCycleCount()).isAtLeast(0);
-        }
-    }
-
-    public void testKernelWakelock() throws Exception {
-        if (!kernelWakelockStatsExist()) {
-            return;
-        }
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.KERNEL_WAKELOCK_FIELD_NUMBER, null);
-
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> data = getGaugeMetricDataList();
-
-        assertThat(data).isNotEmpty();
-        for (Atom atom : data) {
-            assertThat(atom.getKernelWakelock().hasName()).isTrue();
-            assertThat(atom.getKernelWakelock().hasCount()).isTrue();
-            assertThat(atom.getKernelWakelock().hasVersion()).isTrue();
-            assertThat(atom.getKernelWakelock().getVersion()).isGreaterThan(0);
-            assertThat(atom.getKernelWakelock().hasTimeMicros()).isTrue();
-        }
-    }
-
-    // Returns true iff either |WAKE_LOCK_FILE| or |WAKE_SOURCES_FILE| exists.
-    private boolean kernelWakelockStatsExist() {
-      try {
-        return doesFileExist(WAKE_LOCK_FILE) || doesFileExist(WAKE_SOURCES_FILE);
-      } catch(Exception e) {
-        return false;
-      }
-    }
-
-    public void testWifiActivityInfo() throws Exception {
-        if (!hasFeature(FEATURE_WIFI, true)) return;
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        if (!checkDeviceFor("checkWifiEnhancedPowerReportingSupported")) return;
-
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.WIFI_ACTIVITY_INFO_FIELD_NUMBER, null);
-
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> dataList = getGaugeMetricDataList();
-
-        for (Atom atom: dataList) {
-            assertThat(atom.getWifiActivityInfo().getTimestampMillis()).isGreaterThan(0L);
-            assertThat(atom.getWifiActivityInfo().getStackState()).isAtLeast(0);
-            assertThat(atom.getWifiActivityInfo().getControllerIdleTimeMillis()).isGreaterThan(0L);
-            assertThat(atom.getWifiActivityInfo().getControllerTxTimeMillis()).isAtLeast(0L);
-            assertThat(atom.getWifiActivityInfo().getControllerRxTimeMillis()).isAtLeast(0L);
-            assertThat(atom.getWifiActivityInfo().getControllerEnergyUsed()).isAtLeast(0L);
-        }
-    }
-
-    public void testBuildInformation() throws Exception {
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.BUILD_INFORMATION_FIELD_NUMBER, null);
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> data = getGaugeMetricDataList();
-        assertThat(data).isNotEmpty();
-        BuildInformation atom = data.get(0).getBuildInformation();
-        assertThat(getProperty("ro.product.brand")).isEqualTo(atom.getBrand());
-        assertThat(getProperty("ro.product.name")).isEqualTo(atom.getProduct());
-        assertThat(getProperty("ro.product.device")).isEqualTo(atom.getDevice());
-        assertThat(getProperty("ro.build.version.release_or_codename")).isEqualTo(atom.getVersionRelease());
-        assertThat(getProperty("ro.build.id")).isEqualTo(atom.getId());
-        assertThat(getProperty("ro.build.version.incremental"))
-            .isEqualTo(atom.getVersionIncremental());
-        assertThat(getProperty("ro.build.type")).isEqualTo(atom.getType());
-        assertThat(getProperty("ro.build.tags")).isEqualTo(atom.getTags());
-    }
-
-    public void testOnDevicePowerMeasurement() throws Exception {
-        if (!OPTIONAL_TESTS_ENABLED) return;
-
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.ON_DEVICE_POWER_MEASUREMENT_FIELD_NUMBER, null);
-
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> dataList = getGaugeMetricDataList();
-
-        for (Atom atom: dataList) {
-            assertThat(atom.getOnDevicePowerMeasurement().getMeasurementTimestampMillis())
-                .isAtLeast(0L);
-            assertThat(atom.getOnDevicePowerMeasurement().getEnergyMicrowattSecs()).isAtLeast(0L);
-        }
-    }
-
-    // Explicitly tests if the adb command to log a breadcrumb is working.
-    public void testBreadcrumbAdb() throws Exception {
-        final int atomTag = Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER;
-        createAndUploadConfig(atomTag);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
-        assertThat(atom.getLabel()).isEqualTo(1);
-        assertThat(atom.getState().getNumber()).isEqualTo(AppBreadcrumbReported.State.START_VALUE);
-    }
-
-    // Test dumpsys stats --proto.
-    public void testDumpsysStats() throws Exception {
-        final int atomTag = Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER;
-        createAndUploadConfig(atomTag);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Get the stats incident section.
-        List<ConfigMetricsReportList> listList = getReportsFromStatsDataDumpProto();
-        assertThat(listList).isNotEmpty();
-
-        // Extract the relevent report from the incident section.
-        ConfigMetricsReportList ourList = null;
-        int hostUid = getHostUid();
-        for (ConfigMetricsReportList list : listList) {
-            ConfigMetricsReportList.ConfigKey configKey = list.getConfigKey();
-            if (configKey.getUid() == hostUid && configKey.getId() == CONFIG_ID) {
-                ourList = list;
-                break;
-            }
-        }
-        assertWithMessage(String.format("Could not find list for uid=%d id=%d", hostUid, CONFIG_ID))
-            .that(ourList).isNotNull();
-
-        // Make sure that the report is correct.
-        List<EventMetricData> data = getEventMetricDataList(ourList);
-        AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
-        assertThat(atom.getLabel()).isEqualTo(1);
-        assertThat(atom.getState().getNumber()).isEqualTo(AppBreadcrumbReported.State.START_VALUE);
-    }
-
-    public void testConnectivityStateChange() throws Exception {
-        if (!hasFeature(FEATURE_WIFI, true)) return;
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
-
-        final int atomTag = Atom.CONNECTIVITY_STATE_CHANGED_FIELD_NUMBER;
-        createAndUploadConfig(atomTag);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        turnOnAirplaneMode();
-        // wait long enough for airplane mode events to propagate.
-        Thread.sleep(1_200);
-        turnOffAirplaneMode();
-        // wait long enough for the device to restore connection
-        Thread.sleep(13_000);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        // at least 1 disconnect and 1 connect
-        assertThat(data.size()).isAtLeast(2);
-        boolean foundDisconnectEvent = false;
-        boolean foundConnectEvent = false;
-        for (EventMetricData d : data) {
-            ConnectivityStateChanged atom = d.getAtom().getConnectivityStateChanged();
-            if(atom.getState().getNumber()
-                    == ConnectivityStateChanged.State.DISCONNECTED_VALUE) {
-                foundDisconnectEvent = true;
-            }
-            if(atom.getState().getNumber()
-                    == ConnectivityStateChanged.State.CONNECTED_VALUE) {
-                foundConnectEvent = true;
-            }
-        }
-        assertThat(foundConnectEvent).isTrue();
-        assertThat(foundDisconnectEvent).isTrue();
-    }
-
-    public void testSimSlotState() throws Exception {
-        if (!hasFeature(FEATURE_TELEPHONY, true)) {
-            return;
-        }
-
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.SIM_SLOT_STATE_FIELD_NUMBER, null);
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> data = getGaugeMetricDataList();
-        assertThat(data).isNotEmpty();
-        SimSlotState atom = data.get(0).getSimSlotState();
-        // NOTE: it is possible for devices with telephony support to have no SIM at all
-        assertThat(atom.getActiveSlotCount()).isEqualTo(getActiveSimSlotCount());
-        assertThat(atom.getSimCount()).isAtMost(getActiveSimCountUpperBound());
-        assertThat(atom.getEsimCount()).isAtMost(getActiveEsimCountUpperBound());
-        // Above assertions do no necessarily enforce the following, since some are upper bounds
-        assertThat(atom.getActiveSlotCount()).isAtLeast(atom.getSimCount());
-        assertThat(atom.getSimCount()).isAtLeast(atom.getEsimCount());
-        assertThat(atom.getEsimCount()).isAtLeast(0);
-        // For GSM phones, at least one slot should be active even if there is no card
-        if (hasGsmPhone()) {
-            assertThat(atom.getActiveSlotCount()).isAtLeast(1);
-        }
-    }
-
-    public void testSupportedRadioAccessFamily() throws Exception {
-        if (!hasFeature(FEATURE_TELEPHONY, true)) {
-            return;
-        }
-
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.SUPPORTED_RADIO_ACCESS_FAMILY_FIELD_NUMBER, null);
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> data = getGaugeMetricDataList();
-        assertThat(data).isNotEmpty();
-        SupportedRadioAccessFamily atom = data.get(0).getSupportedRadioAccessFamily();
-        if (hasGsmPhone()) {
-            assertThat(atom.getNetworkTypeBitmask() & NETWORK_TYPE_BITMASK_GSM_ALL)
-                    .isNotEqualTo(0L);
-        }
-        if (hasCdmaPhone()) {
-            assertThat(atom.getNetworkTypeBitmask() & NETWORK_TYPE_BITMASK_CDMA_ALL)
-                    .isNotEqualTo(0L);
-        }
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
deleted file mode 100644
index 230a516..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.atom;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.ProcessStateEnum; // From enums.proto for atoms.proto's UidProcessStateChanged.
-
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.EventMetricData;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * Statsd atom tests that are done via app, for atoms that report a uid.
- */
-public class ProcStateAtomTests extends ProcStateTestCase {
-
-    private static final String TAG = "Statsd.ProcStateAtomTests";
-
-    private static final int WAIT_TIME_FOR_CONFIG_UPDATE_MS = 200;
-    // ActivityManager can take a while to register screen state changes, mandating an extra delay.
-    private static final int WAIT_TIME_FOR_CONFIG_AND_SCREEN_MS = 1_000;
-    private static final int EXTRA_WAIT_TIME_MS = 5_000; // as buffer when proc state changing.
-    private static final int STATSD_REPORT_WAIT_TIME_MS = 500; // make sure statsd finishes log.
-
-    private static final String FEATURE_WATCH = "android.hardware.type.watch";
-
-    // The tests here are using the BatteryStats definition of 'background'.
-    private static final Set<Integer> BG_STATES = new HashSet<>(
-            Arrays.asList(
-                    ProcessStateEnum.PROCESS_STATE_IMPORTANT_BACKGROUND_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_TRANSIENT_BACKGROUND_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_BACKUP_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_SERVICE_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_RECEIVER_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_HEAVY_WEIGHT_VALUE
-            ));
-
-    // Using the BatteryStats definition of 'cached', which is why HOME (etc) are considered cached.
-    private static final Set<Integer> CACHED_STATES = new HashSet<>(
-            Arrays.asList(
-                    ProcessStateEnum.PROCESS_STATE_HOME_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_LAST_ACTIVITY_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_CACHED_ACTIVITY_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_CACHED_ACTIVITY_CLIENT_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_CACHED_RECENT_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_CACHED_EMPTY_VALUE
-            ));
-
-    private static final Set<Integer> MISC_STATES = new HashSet<>(
-            Arrays.asList(
-                    ProcessStateEnum.PROCESS_STATE_PERSISTENT_VALUE, // TODO: untested
-                    ProcessStateEnum.PROCESS_STATE_PERSISTENT_UI_VALUE, // TODO: untested
-                    ProcessStateEnum.PROCESS_STATE_TOP_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_BOUND_TOP_VALUE, // TODO: untested
-                    ProcessStateEnum.PROCESS_STATE_BOUND_FOREGROUND_SERVICE_VALUE, // TODO: untested
-                    ProcessStateEnum.PROCESS_STATE_FOREGROUND_SERVICE_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_IMPORTANT_FOREGROUND_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_TOP_SLEEPING_VALUE,
-
-                    ProcessStateEnum.PROCESS_STATE_UNKNOWN_VALUE,
-                    ProcessStateEnum.PROCESS_STATE_NONEXISTENT_VALUE
-            ));
-
-    private static final Set<Integer> ALL_STATES = Stream.of(MISC_STATES, CACHED_STATES, BG_STATES)
-            .flatMap(s -> s.stream()).collect(Collectors.toSet());
-
-    private static final Function<Atom, Integer> PROC_STATE_FUNCTION =
-            atom -> atom.getUidProcessStateChanged().getState().getNumber();
-
-    private static final int PROC_STATE_ATOM_TAG = Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    public void testForegroundService() throws Exception {
-        Set<Integer> onStates = new HashSet<>(Arrays.asList(
-                ProcessStateEnum.PROCESS_STATE_FOREGROUND_SERVICE_VALUE));
-        Set<Integer> offStates = complement(onStates);
-
-        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
-        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
-        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
-
-        executeForegroundService();
-        final int waitTime = SLEEP_OF_FOREGROUND_SERVICE;
-        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS + EXTRA_WAIT_TIME_MS);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
-        assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
-    }
-
-    public void testForeground() throws Exception {
-        Set<Integer> onStates = new HashSet<>(Arrays.asList(
-                ProcessStateEnum.PROCESS_STATE_IMPORTANT_FOREGROUND_VALUE));
-        // There are no offStates, since the app remains in foreground until killed.
-
-        List<Set<Integer>> stateSet = Arrays.asList(onStates); // state sets, in order
-        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
-
-        Thread.sleep(WAIT_TIME_FOR_CONFIG_AND_SCREEN_MS);
-
-        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
-        final int waitTime = EXTRA_WAIT_TIME_MS + 5_000; // Overlay may need to sit there a while.
-        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
-        assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
-    }
-
-    public void testBackground() throws Exception {
-        Set<Integer> onStates = BG_STATES;
-        Set<Integer> offStates = complement(onStates);
-
-        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
-        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
-        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
-
-        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
-        final int waitTime = SLEEP_OF_ACTION_BACKGROUND_SLEEP;
-        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS + EXTRA_WAIT_TIME_MS);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
-        assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
-    }
-
-    public void testTop() throws Exception {
-        Set<Integer> onStates = new HashSet<>(Arrays.asList(
-                ProcessStateEnum.PROCESS_STATE_TOP_VALUE));
-        Set<Integer> offStates = complement(onStates);
-
-        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
-        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
-
-        Thread.sleep(WAIT_TIME_FOR_CONFIG_AND_SCREEN_MS);
-
-        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
-        final int waitTime = SLEEP_OF_ACTION_SLEEP_WHILE_TOP;
-        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS + EXTRA_WAIT_TIME_MS);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
-        assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
-    }
-
-    public void testTopSleeping() throws Exception {
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        Set<Integer> onStates = new HashSet<>(Arrays.asList(
-                ProcessStateEnum.PROCESS_STATE_TOP_SLEEPING_VALUE));
-        Set<Integer> offStates = complement(onStates);
-
-        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
-        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  //False: does not use attribution.
-
-        turnScreenOn();
-        Thread.sleep(WAIT_TIME_FOR_CONFIG_AND_SCREEN_MS);
-
-        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
-        // ASAP, turn off the screen to make proc state -> top_sleeping.
-        turnScreenOff();
-        final int waitTime = SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS;
-        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        popUntilFind(data, new HashSet<>(Arrays.asList(ProcessStateEnum.PROCESS_STATE_TOP_VALUE)),
-                PROC_STATE_FUNCTION); // clear out anything prior to it entering TOP.
-        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out TOP itself.
-        // reset screen back on
-        turnScreenOn();
-        // Don't check the wait time, since it's up to the system how long top sleeping persists.
-        assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
-    }
-
-    public void testCached() throws Exception {
-        Set<Integer> onStates = CACHED_STATES;
-        Set<Integer> offStates = complement(onStates);
-
-        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
-        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: des not use attribution.
-        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
-
-        // The schedule is as follows
-        // #1. The system may do anything it wants, such as moving the app into a cache state.
-        // #2. We move the app into the background.
-        // #3. The background process ends, so the app definitely moves to a cache state
-        //          (this is the ultimate goal of the test).
-        // #4. We start a foreground activity, moving the app out of cache.
-
-        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
-        executeBackgroundService(ACTION_END_IMMEDIATELY);
-        final int cacheTime = 2_000; // process should be in cached state for up to this long
-        Thread.sleep(cacheTime);
-        // Now forcibly bring the app out of cache (#4 above).
-        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
-        // Now check the data *before* the app enters cache again (to avoid another cache event).
-
-        List<EventMetricData> data = getEventMetricDataList();
-        // First, clear out any incidental cached states of step #1, prior to step #2.
-        popUntilFind(data, BG_STATES, PROC_STATE_FUNCTION);
-        // Now clear out the bg state from step #2 (since we are interested in the cache after it).
-        popUntilFind(data, onStates, PROC_STATE_FUNCTION);
-        // The result is that data should start at step #3, definitively in a cached state.
-        assertStatesOccurred(stateSet, data, 1_000, PROC_STATE_FUNCTION);
-    }
-
-    public void testValidityOfStates() throws Exception {
-        assertWithMessage("UNKNOWN_TO_PROTO should not be a valid state")
-            .that(ALL_STATES).doesNotContain(ProcessStateEnum.PROCESS_STATE_UNKNOWN_TO_PROTO_VALUE);
-    }
-
-    /** Returns the a set containing elements of a that are not elements of b. */
-    private Set<Integer> difference(Set<Integer> a, Set<Integer> b) {
-        Set<Integer> result = new HashSet<Integer>(a);
-        result.removeAll(b);
-        return result;
-    }
-
-    /** Returns the set of all states that are not in set. */
-    private Set<Integer> complement(Set<Integer> set) {
-        return difference(ALL_STATES, set);
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
deleted file mode 100644
index 87be4c9..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ /dev/null
@@ -1,2240 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.atom;
-
-import static com.android.os.AtomsProto.IntegrityCheckResultReported.Response.ALLOWED;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.AppOpEnum;
-import android.net.wifi.WifiModeEnum;
-import android.os.WakeLockLevelEnum;
-import android.server.ErrorSource;
-import android.telephony.NetworkTypeEnum;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.util.PropertyUtil;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.AtomsProto;
-import com.android.os.AtomsProto.ANROccurred;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.AppCrashOccurred;
-import com.android.os.AtomsProto.AppOps;
-import com.android.os.AtomsProto.AppStartOccurred;
-import com.android.os.AtomsProto.AppUsageEventOccurred;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.AttributedAppOps;
-import com.android.os.AtomsProto.AttributionNode;
-import com.android.os.AtomsProto.AudioStateChanged;
-import com.android.os.AtomsProto.BinderCalls;
-import com.android.os.AtomsProto.BleScanResultReceived;
-import com.android.os.AtomsProto.BleScanStateChanged;
-import com.android.os.AtomsProto.BlobCommitted;
-import com.android.os.AtomsProto.BlobLeased;
-import com.android.os.AtomsProto.BlobOpened;
-import com.android.os.AtomsProto.CameraStateChanged;
-import com.android.os.AtomsProto.DangerousPermissionState;
-import com.android.os.AtomsProto.DangerousPermissionStateSampled;
-import com.android.os.AtomsProto.DeviceCalculatedPowerBlameUid;
-import com.android.os.AtomsProto.FlashlightStateChanged;
-import com.android.os.AtomsProto.ForegroundServiceAppOpSessionEnded;
-import com.android.os.AtomsProto.ForegroundServiceStateChanged;
-import com.android.os.AtomsProto.GpsScanStateChanged;
-import com.android.os.AtomsProto.HiddenApiUsed;
-import com.android.os.AtomsProto.IntegrityCheckResultReported;
-import com.android.os.AtomsProto.IonHeapSize;
-import com.android.os.AtomsProto.LmkKillOccurred;
-import com.android.os.AtomsProto.LooperStats;
-import com.android.os.AtomsProto.MediaCodecStateChanged;
-import com.android.os.AtomsProto.NotificationReported;
-import com.android.os.AtomsProto.OverlayStateChanged;
-import com.android.os.AtomsProto.PackageNotificationChannelGroupPreferences;
-import com.android.os.AtomsProto.PackageNotificationChannelPreferences;
-import com.android.os.AtomsProto.PackageNotificationPreferences;
-import com.android.os.AtomsProto.PictureInPictureStateChanged;
-import com.android.os.AtomsProto.ProcessMemoryHighWaterMark;
-import com.android.os.AtomsProto.ProcessMemorySnapshot;
-import com.android.os.AtomsProto.ProcessMemoryState;
-import com.android.os.AtomsProto.ScheduledJobStateChanged;
-import com.android.os.AtomsProto.SettingSnapshot;
-import com.android.os.AtomsProto.SyncStateChanged;
-import com.android.os.AtomsProto.TestAtomReported;
-import com.android.os.AtomsProto.UiEventReported;
-import com.android.os.AtomsProto.VibratorStateChanged;
-import com.android.os.AtomsProto.WakelockStateChanged;
-import com.android.os.AtomsProto.WakeupAlarmOccurred;
-import com.android.os.AtomsProto.WifiLockStateChanged;
-import com.android.os.AtomsProto.WifiMulticastLockStateChanged;
-import com.android.os.AtomsProto.WifiScanStateChanged;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.server.notification.SmallHash;
-import com.android.tradefed.log.LogUtil;
-
-import com.google.common.collect.Range;
-import com.google.protobuf.Descriptors;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-/**
- * Statsd atom tests that are done via app, for atoms that report a uid.
- */
-public class UidAtomTests extends DeviceAtomTestCase {
-
-    private static final String TAG = "Statsd.UidAtomTests";
-
-    private static final String TEST_PACKAGE_NAME = "com.android.server.cts.device.statsd";
-
-    private static final boolean DAVEY_ENABLED = false;
-
-    private static final int NUM_APP_OPS = AttributedAppOps.getDefaultInstance().getOp().
-            getDescriptorForType().getValues().size() - 1;
-
-    private static final String TEST_INSTALL_APK = "CtsStatsdEmptyApp.apk";
-    private static final String TEST_INSTALL_APK_BASE = "CtsStatsdEmptySplitApp.apk";
-    private static final String TEST_INSTALL_APK_SPLIT = "CtsStatsdEmptySplitApp_pl.apk";
-    private static final String TEST_INSTALL_PACKAGE =
-            "com.android.cts.device.statsd.emptyapp";
-    private static final String TEST_REMOTE_DIR = "/data/local/tmp/statsd";
-    private static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
-    private static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
-
-    private static final int WAIT_TIME_FOR_CONFIG_UPDATE_MS = 200;
-    private static final int EXTRA_WAIT_TIME_MS = 5_000; // as buffer when app starting/stopping.
-    private static final int STATSD_REPORT_WAIT_TIME_MS = 500; // make sure statsd finishes log.
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        resetBatteryStatus();
-        super.tearDown();
-    }
-
-    public void testLmkKillOccurred() throws Exception {
-        if (!"true".equals(getProperty("ro.lmk.log_stats"))) {
-            return;
-        }
-
-        final int atomTag = Atom.LMK_KILL_OCCURRED_FIELD_NUMBER;
-        createAndUploadConfig(atomTag, false);
-
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        executeBackgroundService(ACTION_LMK);
-        Thread.sleep(15_000);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        assertThat(data).hasSize(1);
-        assertThat(data.get(0).getAtom().hasLmkKillOccurred()).isTrue();
-        LmkKillOccurred atom = data.get(0).getAtom().getLmkKillOccurred();
-        assertThat(atom.getUid()).isEqualTo(getUid());
-        assertThat(atom.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
-        assertThat(atom.getOomAdjScore()).isAtLeast(500);
-    }
-
-    public void testAppCrashOccurred() throws Exception {
-        final int atomTag = Atom.APP_CRASH_OCCURRED_FIELD_NUMBER;
-        createAndUploadConfig(atomTag, false);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runActivity("StatsdCtsForegroundActivity", "action", "action.crash");
-
-        Thread.sleep(WAIT_TIME_SHORT);
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        AppCrashOccurred atom = data.get(0).getAtom().getAppCrashOccurred();
-        assertThat(atom.getEventType()).isEqualTo("crash");
-        assertThat(atom.getIsInstantApp().getNumber())
-            .isEqualTo(AppCrashOccurred.InstantApp.FALSE_VALUE);
-        assertThat(atom.getForegroundState().getNumber())
-            .isEqualTo(AppCrashOccurred.ForegroundState.FOREGROUND_VALUE);
-        assertThat(atom.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
-    }
-
-    public void testAppStartOccurred() throws Exception {
-        final int atomTag = Atom.APP_START_OCCURRED_FIELD_NUMBER;
-
-        createAndUploadConfig(atomTag, false);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runActivity("StatsdCtsForegroundActivity", "action", "action.sleep_top", 3_500);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        assertThat(data).hasSize(1);
-        AppStartOccurred atom = data.get(0).getAtom().getAppStartOccurred();
-        assertThat(atom.getPkgName()).isEqualTo(TEST_PACKAGE_NAME);
-        assertThat(atom.getActivityName())
-            .isEqualTo("com.android.server.cts.device.statsd.StatsdCtsForegroundActivity");
-        assertThat(atom.getIsInstantApp()).isFalse();
-        assertThat(atom.getActivityStartMillis()).isGreaterThan(0L);
-        assertThat(atom.getTransitionDelayMillis()).isGreaterThan(0);
-    }
-
-    public void testAudioState() throws Exception {
-        if (!hasFeature(FEATURE_AUDIO_OUTPUT, true)) return;
-
-        final int atomTag = Atom.AUDIO_STATE_CHANGED_FIELD_NUMBER;
-        final String name = "testAudioState";
-
-        Set<Integer> onState = new HashSet<>(
-                Arrays.asList(AudioStateChanged.State.ON_VALUE));
-        Set<Integer> offState = new HashSet<>(
-                Arrays.asList(AudioStateChanged.State.OFF_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
-
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
-
-        Thread.sleep(WAIT_TIME_SHORT);
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Because the timestamp is truncated, we skip checking time differences between state
-        // changes.
-        assertStatesOccurred(stateSet, data, 0,
-                atom -> atom.getAudioStateChanged().getState().getNumber());
-
-        // Check that timestamp is truncated
-        for (EventMetricData metric : data) {
-            long elapsedTimestampNs = metric.getElapsedTimestampNanos();
-            assertTimestampIsTruncated(elapsedTimestampNs);
-        }
-    }
-
-    public void testBleScan() throws Exception {
-        if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
-
-        final int atom = Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER;
-        final int field = BleScanStateChanged.STATE_FIELD_NUMBER;
-        final int stateOn = BleScanStateChanged.State.ON_VALUE;
-        final int stateOff = BleScanStateChanged.State.OFF_VALUE;
-        final int minTimeDiffMillis = 1_500;
-        final int maxTimeDiffMillis = 3_000;
-
-        List<EventMetricData> data = doDeviceMethodOnOff("testBleScanUnoptimized", atom, field,
-                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
-
-        BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
-        BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
-        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
-        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
-    }
-
-    public void testBleUnoptimizedScan() throws Exception {
-        if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
-
-        final int atom = Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER;
-        final int field = BleScanStateChanged.STATE_FIELD_NUMBER;
-        final int stateOn = BleScanStateChanged.State.ON_VALUE;
-        final int stateOff = BleScanStateChanged.State.OFF_VALUE;
-        final int minTimeDiffMillis = 1_500;
-        final int maxTimeDiffMillis = 3_000;
-
-        List<EventMetricData> data = doDeviceMethodOnOff("testBleScanUnoptimized", atom, field,
-                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
-
-        BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
-        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
-        assertThat(a0.getIsFiltered()).isFalse();
-        assertThat(a0.getIsFirstMatch()).isFalse();
-        assertThat(a0.getIsOpportunistic()).isFalse();
-        BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
-        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
-        assertThat(a1.getIsFiltered()).isFalse();
-        assertThat(a1.getIsFirstMatch()).isFalse();
-        assertThat(a1.getIsOpportunistic()).isFalse();
-
-
-        // Now repeat the test for opportunistic scanning and make sure it is reported correctly.
-        data = doDeviceMethodOnOff("testBleScanOpportunistic", atom, field,
-                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
-
-        a0 = data.get(0).getAtom().getBleScanStateChanged();
-        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
-        assertThat(a0.getIsFiltered()).isFalse();
-        assertThat(a0.getIsFirstMatch()).isFalse();
-        assertThat(a0.getIsOpportunistic()).isTrue();  // This scan is opportunistic.
-        a1 = data.get(1).getAtom().getBleScanStateChanged();
-        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
-        assertThat(a1.getIsFiltered()).isFalse();
-        assertThat(a1.getIsFirstMatch()).isFalse();
-        assertThat(a1.getIsOpportunistic()).isTrue();
-    }
-
-    public void testBleScanResult() throws Exception {
-        if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
-
-        final int atom = Atom.BLE_SCAN_RESULT_RECEIVED_FIELD_NUMBER;
-        final int field = BleScanResultReceived.NUM_RESULTS_FIELD_NUMBER;
-
-        StatsdConfig.Builder conf = createConfigBuilder();
-        addAtomEvent(conf, atom, createFvm(field).setGteInt(0));
-        List<EventMetricData> data = doDeviceMethod("testBleScanResult", conf);
-
-        assertThat(data.size()).isAtLeast(1);
-        BleScanResultReceived a0 = data.get(0).getAtom().getBleScanResultReceived();
-        assertThat(a0.getNumResults()).isAtLeast(1);
-    }
-
-    public void testHiddenApiUsed() throws Exception {
-        String oldRate = getDevice().executeShellCommand(
-                "device_config get app_compat hidden_api_access_statslog_sampling_rate").trim();
-
-        getDevice().executeShellCommand(
-                "device_config put app_compat hidden_api_access_statslog_sampling_rate 65536");
-
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        try {
-            final int atomTag = Atom.HIDDEN_API_USED_FIELD_NUMBER;
-
-            createAndUploadConfig(atomTag, false);
-
-            runActivity("HiddenApiUsedActivity", null, null, 2_500);
-
-            List<EventMetricData> data = getEventMetricDataList();
-            assertThat(data).hasSize(1);
-
-            HiddenApiUsed atom = data.get(0).getAtom().getHiddenApiUsed();
-
-            int uid = getUid();
-            assertThat(atom.getUid()).isEqualTo(uid);
-            assertThat(atom.getAccessDenied()).isFalse();
-            assertThat(atom.getSignature())
-                .isEqualTo("Landroid/app/Activity;->mWindow:Landroid/view/Window;");
-        } finally {
-            if (!oldRate.equals("null")) {
-                getDevice().executeShellCommand(
-                        "device_config put app_compat hidden_api_access_statslog_sampling_rate "
-                        + oldRate);
-            } else {
-                getDevice().executeShellCommand(
-                        "device_config delete hidden_api_access_statslog_sampling_rate");
-            }
-        }
-    }
-
-    public void testCameraState() throws Exception {
-        if (!hasFeature(FEATURE_CAMERA, true) && !hasFeature(FEATURE_CAMERA_FRONT, true)) return;
-
-        final int atomTag = Atom.CAMERA_STATE_CHANGED_FIELD_NUMBER;
-        Set<Integer> cameraOn = new HashSet<>(Arrays.asList(CameraStateChanged.State.ON_VALUE));
-        Set<Integer> cameraOff = new HashSet<>(Arrays.asList(CameraStateChanged.State.OFF_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(cameraOn, cameraOff);
-
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testCameraState");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
-                atom -> atom.getCameraStateChanged().getState().getNumber());
-    }
-
-    public void testCpuTimePerUid() throws Exception {
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.CPU_TIME_PER_UID_FIELD_NUMBER, null);
-
-        uploadConfig(config);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
-
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> atomList = getGaugeMetricDataList();
-
-        // TODO: We don't have atom matching on gauge yet. Let's refactor this after that feature is
-        // implemented.
-        boolean found = false;
-        int uid = getUid();
-        for (Atom atom : atomList) {
-            if (atom.getCpuTimePerUid().getUid() == uid) {
-                found = true;
-                assertThat(atom.getCpuTimePerUid().getUserTimeMicros()).isGreaterThan(0L);
-                assertThat(atom.getCpuTimePerUid().getSysTimeMicros()).isGreaterThan(0L);
-            }
-        }
-        assertWithMessage(String.format("did not find uid %d", uid)).that(found).isTrue();
-    }
-
-    public void testDeviceCalculatedPowerUse() throws Exception {
-        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
-
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.DEVICE_CALCULATED_POWER_USE_FIELD_NUMBER, null);
-        uploadConfig(config);
-        unplugDevice();
-
-        Thread.sleep(WAIT_TIME_LONG);
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        Atom atom = getGaugeMetricDataList().get(0);
-        assertThat(atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs())
-            .isGreaterThan(0L);
-    }
-
-
-    public void testDeviceCalculatedPowerBlameUid() throws Exception {
-        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
-        if (!hasBattery()) {
-            return;
-        }
-
-        String kernelVersion = getDevice().executeShellCommand("uname -r");
-        if (kernelVersion.contains("3.18")) {
-            LogUtil.CLog.d("Skipping calculated power blame uid test.");
-            return;
-        }
-
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config,
-                Atom.DEVICE_CALCULATED_POWER_BLAME_UID_FIELD_NUMBER, null);
-        uploadConfig(config);
-        unplugDevice();
-
-        Thread.sleep(WAIT_TIME_LONG);
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        List<Atom> atomList = getGaugeMetricDataList();
-        boolean uidFound = false;
-        int uid = getUid();
-        long uidPower = 0;
-        for (Atom atom : atomList) {
-            DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
-                if (item.getUid() == uid) {
-                assertWithMessage(String.format("Found multiple power values for uid %d", uid))
-                    .that(uidFound).isFalse();
-                uidFound = true;
-                uidPower = item.getPowerNanoAmpSecs();
-            }
-        }
-        assertWithMessage(String.format("No power value for uid %d", uid)).that(uidFound).isTrue();
-        assertWithMessage(String.format("Non-positive power value for uid %d", uid))
-            .that(uidPower).isGreaterThan(0L);
-    }
-
-    public void testDavey() throws Exception {
-        if (!DAVEY_ENABLED ) return;
-        long MAX_DURATION = 2000;
-        long MIN_DURATION = 750;
-        final int atomTag = Atom.DAVEY_OCCURRED_FIELD_NUMBER;
-        createAndUploadConfig(atomTag, false); // UID is logged without attribution node
-
-        runActivity("DaveyActivity", null, null);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        assertThat(data).hasSize(1);
-        long duration = data.get(0).getAtom().getDaveyOccurred().getJankDurationMillis();
-        assertWithMessage("Incorrect jank duration")
-            .that(duration).isIn(Range.closed(MIN_DURATION, MAX_DURATION));
-    }
-
-    public void testFlashlightState() throws Exception {
-        if (!hasFeature(FEATURE_CAMERA_FLASH, true)) return;
-
-        final int atomTag = Atom.FLASHLIGHT_STATE_CHANGED_FIELD_NUMBER;
-        final String name = "testFlashlight";
-
-        Set<Integer> flashlightOn = new HashSet<>(
-            Arrays.asList(FlashlightStateChanged.State.ON_VALUE));
-        Set<Integer> flashlightOff = new HashSet<>(
-            Arrays.asList(FlashlightStateChanged.State.OFF_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(flashlightOn, flashlightOff);
-
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-                atom -> atom.getFlashlightStateChanged().getState().getNumber());
-    }
-
-    public void testForegroundServiceState() throws Exception {
-        final int atomTag = Atom.FOREGROUND_SERVICE_STATE_CHANGED_FIELD_NUMBER;
-        final String name = "testForegroundService";
-
-        Set<Integer> enterForeground = new HashSet<>(
-                Arrays.asList(ForegroundServiceStateChanged.State.ENTER_VALUE));
-        Set<Integer> exitForeground = new HashSet<>(
-                Arrays.asList(ForegroundServiceStateChanged.State.EXIT_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(enterForeground, exitForeground);
-
-        createAndUploadConfig(atomTag, false);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-                atom -> atom.getForegroundServiceStateChanged().getState().getNumber());
-    }
-
-
-    public void testForegroundServiceAccessAppOp() throws Exception {
-        final int atomTag = Atom.FOREGROUND_SERVICE_APP_OP_SESSION_ENDED_FIELD_NUMBER;
-        final String name = "testForegroundServiceAccessAppOp";
-
-        createAndUploadConfig(atomTag, false);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        assertWithMessage("Wrong atom size").that(data.size()).isEqualTo(3);
-        for (int i = 0; i < data.size(); i++) {
-            ForegroundServiceAppOpSessionEnded atom
-                    = data.get(i).getAtom().getForegroundServiceAppOpSessionEnded();
-            final int opName = atom.getAppOpName().getNumber();
-            final int acceptances = atom.getCountOpsAccepted();
-            final int rejections = atom.getCountOpsRejected();
-            final int count = acceptances + rejections;
-            int expectedCount = 0;
-            switch (opName) {
-                case AppOpEnum.APP_OP_CAMERA_VALUE:
-                    expectedCount = 3;
-                    break;
-                case AppOpEnum.APP_OP_FINE_LOCATION_VALUE:
-                    expectedCount = 1;
-                    break;
-                case AppOpEnum.APP_OP_RECORD_AUDIO_VALUE:
-                    expectedCount = 2;
-                    break;
-                case AppOpEnum.APP_OP_COARSE_LOCATION_VALUE:
-                    // fall-through
-                default:
-                    fail("Unexpected opName " + opName);
-            }
-            assertWithMessage("Wrong count for " + opName).that(count).isEqualTo(expectedCount);
-        }
-    }
-
-    public void testGpsScan() throws Exception {
-        if (!hasFeature(FEATURE_LOCATION_GPS, true)) return;
-        // Whitelist this app against background location request throttling
-        String origWhitelist = getDevice().executeShellCommand(
-                "settings get global location_background_throttle_package_whitelist").trim();
-        getDevice().executeShellCommand(String.format(
-                "settings put global location_background_throttle_package_whitelist %s",
-                DEVICE_SIDE_TEST_PACKAGE));
-
-        try {
-            final int atom = Atom.GPS_SCAN_STATE_CHANGED_FIELD_NUMBER;
-            final int key = GpsScanStateChanged.STATE_FIELD_NUMBER;
-            final int stateOn = GpsScanStateChanged.State.ON_VALUE;
-            final int stateOff = GpsScanStateChanged.State.OFF_VALUE;
-            final int minTimeDiffMillis = 500;
-            final int maxTimeDiffMillis = 60_000;
-
-            List<EventMetricData> data = doDeviceMethodOnOff("testGpsScan", atom, key,
-                    stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
-
-            GpsScanStateChanged a0 = data.get(0).getAtom().getGpsScanStateChanged();
-            GpsScanStateChanged a1 = data.get(1).getAtom().getGpsScanStateChanged();
-            assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
-            assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
-        } finally {
-            if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
-                getDevice().executeShellCommand(
-                        "settings delete global location_background_throttle_package_whitelist");
-            } else {
-                getDevice().executeShellCommand(String.format(
-                        "settings put global location_background_throttle_package_whitelist %s",
-                        origWhitelist));
-            }
-        }
-    }
-
-    public void testGnssStats() throws Exception {
-        // Get GnssMetrics as a simple gauge metric.
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.GNSS_STATS_FIELD_NUMBER, null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        if (!hasFeature(FEATURE_LOCATION_GPS, true)) return;
-        // Whitelist this app against background location request throttling
-        String origWhitelist = getDevice().executeShellCommand(
-                "settings get global location_background_throttle_package_whitelist").trim();
-        getDevice().executeShellCommand(String.format(
-                "settings put global location_background_throttle_package_whitelist %s",
-                DEVICE_SIDE_TEST_PACKAGE));
-
-        try {
-            runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testGpsStatus");
-
-            Thread.sleep(WAIT_TIME_LONG);
-            // Trigger a pull and wait for new pull before killing the process.
-            setAppBreadcrumbPredicate();
-            Thread.sleep(WAIT_TIME_LONG);
-
-            // Assert about GnssMetrics for the test app.
-            List<Atom> atoms = getGaugeMetricDataList();
-
-            boolean found = false;
-            for (Atom atom : atoms) {
-                AtomsProto.GnssStats state = atom.getGnssStats();
-                found = true;
-                if ((state.getSvStatusReports() > 0 || state.getL5SvStatusReports() > 0)
-                        && state.getLocationReports() == 0) {
-                    // Device is detected to be indoors and not able to acquire location.
-                    // flaky test device
-                    break;
-                }
-                assertThat(state.getLocationReports()).isGreaterThan((long) 0);
-                assertThat(state.getLocationFailureReports()).isAtLeast((long) 0);
-                assertThat(state.getTimeToFirstFixReports()).isGreaterThan((long) 0);
-                assertThat(state.getTimeToFirstFixMillis()).isGreaterThan((long) 0);
-                assertThat(state.getPositionAccuracyReports()).isGreaterThan((long) 0);
-                assertThat(state.getPositionAccuracyMeters()).isGreaterThan((long) 0);
-                assertThat(state.getTopFourAverageCn0Reports()).isGreaterThan((long) 0);
-                assertThat(state.getTopFourAverageCn0DbMhz()).isGreaterThan((long) 0);
-                assertThat(state.getL5TopFourAverageCn0Reports()).isAtLeast((long) 0);
-                assertThat(state.getL5TopFourAverageCn0DbMhz()).isAtLeast((long) 0);
-                assertThat(state.getSvStatusReports()).isAtLeast((long) 0);
-                assertThat(state.getSvStatusReportsUsedInFix()).isAtLeast((long) 0);
-                assertThat(state.getL5SvStatusReports()).isAtLeast((long) 0);
-                assertThat(state.getL5SvStatusReportsUsedInFix()).isAtLeast((long) 0);
-            }
-            assertWithMessage(String.format("Did not find a matching atom"))
-                    .that(found).isTrue();
-        } finally {
-            if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
-                getDevice().executeShellCommand(
-                        "settings delete global location_background_throttle_package_whitelist");
-            } else {
-                getDevice().executeShellCommand(String.format(
-                        "settings put global location_background_throttle_package_whitelist %s",
-                        origWhitelist));
-            }
-        }
-    }
-
-    public void testMediaCodecActivity() throws Exception {
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        final int atomTag = Atom.MEDIA_CODEC_STATE_CHANGED_FIELD_NUMBER;
-
-        // 5 seconds. Starting video tends to be much slower than most other
-        // tests on slow devices. This is unfortunate, because it leaves a
-        // really big slop in assertStatesOccurred.  It would be better if
-        // assertStatesOccurred had a tighter range on large timeouts.
-        final int waitTime = 5000;
-
-        // From {@link VideoPlayerActivity#DELAY_MILLIS}
-        final int videoDuration = 2000;
-
-        Set<Integer> onState = new HashSet<>(
-                Arrays.asList(MediaCodecStateChanged.State.ON_VALUE));
-        Set<Integer> offState = new HashSet<>(
-                Arrays.asList(MediaCodecStateChanged.State.OFF_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
-
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runActivity("VideoPlayerActivity", "action", "action.play_video",
-            waitTime);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, videoDuration,
-                atom -> atom.getMediaCodecStateChanged().getState().getNumber());
-    }
-
-    public void testOverlayState() throws Exception {
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        final int atomTag = Atom.OVERLAY_STATE_CHANGED_FIELD_NUMBER;
-
-        Set<Integer> entered = new HashSet<>(
-                Arrays.asList(OverlayStateChanged.State.ENTERED_VALUE));
-        Set<Integer> exited = new HashSet<>(
-                Arrays.asList(OverlayStateChanged.State.EXITED_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(entered, exited);
-
-        createAndUploadConfig(atomTag, false);
-
-        runActivity("StatsdCtsForegroundActivity", "action", "action.show_application_overlay",
-                5_000);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        // The overlay box should appear about 2sec after the app start
-        assertStatesOccurred(stateSet, data, 0,
-                atom -> atom.getOverlayStateChanged().getState().getNumber());
-    }
-
-    public void testPictureInPictureState() throws Exception {
-        String supported = getDevice().executeShellCommand("am supports-multiwindow");
-        if (!hasFeature(FEATURE_WATCH, false) ||
-                !hasFeature(FEATURE_PICTURE_IN_PICTURE, true) ||
-                !supported.contains("true")) {
-            LogUtil.CLog.d("Skipping picture in picture atom test.");
-            return;
-        }
-
-        StatsdConfig.Builder conf = createConfigBuilder();
-        // PictureInPictureStateChanged atom is used prior to rvc-qpr
-        addAtomEvent(conf, Atom.PICTURE_IN_PICTURE_STATE_CHANGED_FIELD_NUMBER,
-                /*useAttribution=*/false);
-        // Picture-in-picture logs' been migrated to UiEvent since rvc-qpr
-        FieldValueMatcher.Builder pkgMatcher = createFvm(UiEventReported.PACKAGE_NAME_FIELD_NUMBER)
-                .setEqString(DEVICE_SIDE_TEST_PACKAGE);
-        addAtomEvent(conf, Atom.UI_EVENT_REPORTED_FIELD_NUMBER, Arrays.asList(pkgMatcher));
-        uploadConfig(conf);
-
-        LogUtil.CLog.d("Playing video in Picture-in-Picture mode");
-        runActivity("VideoPlayerActivity", "action", "action.play_video_picture_in_picture_mode");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Filter out the PictureInPictureStateChanged and UiEventReported atom
-        List<EventMetricData> pictureInPictureStateChangedData = data.stream()
-                .filter(e -> e.getAtom().hasPictureInPictureStateChanged())
-                .collect(Collectors.toList());
-        List<EventMetricData> uiEventReportedData = data.stream()
-                .filter(e -> e.getAtom().hasUiEventReported())
-                .collect(Collectors.toList());
-
-        if (!pictureInPictureStateChangedData.isEmpty()) {
-            LogUtil.CLog.d("Assert using PictureInPictureStateChanged");
-            Set<Integer> entered = new HashSet<>(
-                    Arrays.asList(PictureInPictureStateChanged.State.ENTERED_VALUE));
-            List<Set<Integer>> stateSet = Arrays.asList(entered);
-            assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
-                    atom -> atom.getPictureInPictureStateChanged().getState().getNumber());
-        } else if (!uiEventReportedData.isEmpty()) {
-            LogUtil.CLog.d("Assert using UiEventReported");
-            // See PipUiEventEnum for definitions
-            final int enterPipEventId = 603;
-            // Assert that log for entering PiP happens exactly once, we do not use
-            // assertStateOccurred here since PiP may log something else when activity finishes.
-            List<EventMetricData> entered = uiEventReportedData.stream()
-                    .filter(e -> e.getAtom().getUiEventReported().getEventId() == enterPipEventId)
-                    .collect(Collectors.toList());
-            assertThat(entered).hasSize(1);
-        } else {
-            fail("No logging event from PictureInPictureStateChanged nor UiEventReported");
-        }
-    }
-
-    public void testScheduledJobState() throws Exception {
-        String expectedName = "com.android.server.cts.device.statsd/.StatsdJobService";
-        final int atomTag = Atom.SCHEDULED_JOB_STATE_CHANGED_FIELD_NUMBER;
-        Set<Integer> jobSchedule = new HashSet<>(
-                Arrays.asList(ScheduledJobStateChanged.State.SCHEDULED_VALUE));
-        Set<Integer> jobOn = new HashSet<>(
-                Arrays.asList(ScheduledJobStateChanged.State.STARTED_VALUE));
-        Set<Integer> jobOff = new HashSet<>(
-                Arrays.asList(ScheduledJobStateChanged.State.FINISHED_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(jobSchedule, jobOn, jobOff);
-
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        allowImmediateSyncs();
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testScheduledJob");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        assertStatesOccurred(stateSet, data, 0,
-                atom -> atom.getScheduledJobStateChanged().getState().getNumber());
-
-        for (EventMetricData e : data) {
-            assertThat(e.getAtom().getScheduledJobStateChanged().getJobName())
-                .isEqualTo(expectedName);
-        }
-    }
-
-    //Note: this test does not have uid, but must run on the device
-    public void testScreenBrightness() throws Exception {
-        int initialBrightness = getScreenBrightness();
-        boolean isInitialManual = isScreenBrightnessModeManual();
-        setScreenBrightnessMode(true);
-        setScreenBrightness(200);
-        Thread.sleep(WAIT_TIME_LONG);
-
-        final int atomTag = Atom.SCREEN_BRIGHTNESS_CHANGED_FIELD_NUMBER;
-
-        Set<Integer> screenMin = new HashSet<>(Arrays.asList(47));
-        Set<Integer> screen100 = new HashSet<>(Arrays.asList(100));
-        Set<Integer> screen200 = new HashSet<>(Arrays.asList(198));
-        // Set<Integer> screenMax = new HashSet<>(Arrays.asList(255));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(screenMin, screen100, screen200);
-
-        createAndUploadConfig(atomTag);
-        Thread.sleep(WAIT_TIME_SHORT);
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testScreenBrightness");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Restore initial screen brightness
-        setScreenBrightness(initialBrightness);
-        setScreenBrightnessMode(isInitialManual);
-
-        popUntilFind(data, screenMin, atom->atom.getScreenBrightnessChanged().getLevel());
-        popUntilFindFromEnd(data, screen200, atom->atom.getScreenBrightnessChanged().getLevel());
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-            atom -> atom.getScreenBrightnessChanged().getLevel());
-    }
-    public void testSyncState() throws Exception {
-        final int atomTag = Atom.SYNC_STATE_CHANGED_FIELD_NUMBER;
-        Set<Integer> syncOn = new HashSet<>(Arrays.asList(SyncStateChanged.State.ON_VALUE));
-        Set<Integer> syncOff = new HashSet<>(Arrays.asList(SyncStateChanged.State.OFF_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(syncOn, syncOff, syncOn, syncOff);
-
-        createAndUploadConfig(atomTag, true);
-        allowImmediateSyncs();
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSyncState");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data,
-            /* wait = */ 0 /* don't verify time differences between state changes */,
-            atom -> atom.getSyncStateChanged().getState().getNumber());
-    }
-
-    public void testVibratorState() throws Exception {
-        if (!checkDeviceFor("checkVibratorSupported")) return;
-
-        final int atomTag = Atom.VIBRATOR_STATE_CHANGED_FIELD_NUMBER;
-        final String name = "testVibratorState";
-
-        Set<Integer> onState = new HashSet<>(
-                Arrays.asList(VibratorStateChanged.State.ON_VALUE));
-        Set<Integer> offState = new HashSet<>(
-                Arrays.asList(VibratorStateChanged.State.OFF_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
-
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
-
-        Thread.sleep(WAIT_TIME_LONG);
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        assertStatesOccurred(stateSet, data, 300,
-                atom -> atom.getVibratorStateChanged().getState().getNumber());
-    }
-
-    public void testWakelockState() throws Exception {
-        final int atomTag = Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER;
-        Set<Integer> wakelockOn = new HashSet<>(Arrays.asList(
-                WakelockStateChanged.State.ACQUIRE_VALUE,
-                WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE));
-        Set<Integer> wakelockOff = new HashSet<>(Arrays.asList(
-                WakelockStateChanged.State.RELEASE_VALUE,
-                WakelockStateChanged.State.CHANGE_RELEASE_VALUE));
-
-        final String EXPECTED_TAG = "StatsdPartialWakelock";
-        final WakeLockLevelEnum EXPECTED_LEVEL = WakeLockLevelEnum.PARTIAL_WAKE_LOCK;
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(wakelockOn, wakelockOff);
-
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockState");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-            atom -> atom.getWakelockStateChanged().getState().getNumber());
-
-        for (EventMetricData event: data) {
-            String tag = event.getAtom().getWakelockStateChanged().getTag();
-            WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getType();
-            assertThat(tag).isEqualTo(EXPECTED_TAG);
-            assertThat(type).isEqualTo(EXPECTED_LEVEL);
-        }
-    }
-
-    public void testWakeupAlarm() throws Exception {
-        // For automotive, all wakeup alarm becomes normal alarm. So this
-        // test does not work.
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
-        final int atomTag = Atom.WAKEUP_ALARM_OCCURRED_FIELD_NUMBER;
-
-        StatsdConfig.Builder config = createConfigBuilder();
-        addAtomEvent(config, atomTag, true);  // True: uses attribution.
-
-        List<EventMetricData> data = doDeviceMethod("testWakeupAlarm", config);
-        assertThat(data.size()).isAtLeast(1);
-        for (int i = 0; i < data.size(); i++) {
-            WakeupAlarmOccurred wao = data.get(i).getAtom().getWakeupAlarmOccurred();
-            assertThat(wao.getTag()).isEqualTo("*walarm*:android.cts.statsd.testWakeupAlarm");
-            assertThat(wao.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
-        }
-    }
-
-    public void testWifiLockHighPerf() throws Exception {
-        if (!hasFeature(FEATURE_WIFI, true)) return;
-        if (!hasFeature(FEATURE_PC, false)) return;
-
-        final int atomTag = Atom.WIFI_LOCK_STATE_CHANGED_FIELD_NUMBER;
-        Set<Integer> lockOn = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.ON_VALUE));
-        Set<Integer> lockOff = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.OFF_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
-
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWifiLockHighPerf");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-                atom -> atom.getWifiLockStateChanged().getState().getNumber());
-
-        for (EventMetricData event : data) {
-            assertThat(event.getAtom().getWifiLockStateChanged().getMode())
-                .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_HIGH_PERF);
-        }
-    }
-
-    public void testWifiLockLowLatency() throws Exception {
-        if (!hasFeature(FEATURE_WIFI, true)) return;
-        if (!hasFeature(FEATURE_PC, false)) return;
-
-        final int atomTag = Atom.WIFI_LOCK_STATE_CHANGED_FIELD_NUMBER;
-        Set<Integer> lockOn = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.ON_VALUE));
-        Set<Integer> lockOff = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.OFF_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
-
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWifiLockLowLatency");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-                atom -> atom.getWifiLockStateChanged().getState().getNumber());
-
-        for (EventMetricData event : data) {
-            assertThat(event.getAtom().getWifiLockStateChanged().getMode())
-                .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_LOW_LATENCY);
-        }
-    }
-
-    public void testWifiMulticastLock() throws Exception {
-        if (!hasFeature(FEATURE_WIFI, true)) return;
-        if (!hasFeature(FEATURE_PC, false)) return;
-
-        final int atomTag = Atom.WIFI_MULTICAST_LOCK_STATE_CHANGED_FIELD_NUMBER;
-        Set<Integer> lockOn = new HashSet<>(
-                Arrays.asList(WifiMulticastLockStateChanged.State.ON_VALUE));
-        Set<Integer> lockOff = new HashSet<>(
-                Arrays.asList(WifiMulticastLockStateChanged.State.OFF_VALUE));
-
-        final String EXPECTED_TAG = "StatsdCTSMulticastLock";
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
-
-        createAndUploadConfig(atomTag, true);
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWifiMulticastLock");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-                atom -> atom.getWifiMulticastLockStateChanged().getState().getNumber());
-
-        for (EventMetricData event: data) {
-            String tag = event.getAtom().getWifiMulticastLockStateChanged().getTag();
-            assertThat(tag).isEqualTo(EXPECTED_TAG);
-        }
-    }
-
-    public void testWifiScan() throws Exception {
-        if (!hasFeature(FEATURE_WIFI, true)) return;
-
-        final int atom = Atom.WIFI_SCAN_STATE_CHANGED_FIELD_NUMBER;
-        final int key = WifiScanStateChanged.STATE_FIELD_NUMBER;
-        final int stateOn = WifiScanStateChanged.State.ON_VALUE;
-        final int stateOff = WifiScanStateChanged.State.OFF_VALUE;
-        final int minTimeDiffMillis = 250;
-        final int maxTimeDiffMillis = 60_000;
-        final boolean demandExactlyTwo = false; // Two scans are performed, so up to 4 atoms logged.
-
-        List<EventMetricData> data = doDeviceMethodOnOff("testWifiScan", atom, key,
-                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, demandExactlyTwo);
-
-        assertThat(data.size()).isIn(Range.closed(2, 4));
-        WifiScanStateChanged a0 = data.get(0).getAtom().getWifiScanStateChanged();
-        WifiScanStateChanged a1 = data.get(1).getAtom().getWifiScanStateChanged();
-        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
-        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
-    }
-
-    public void testBinderStats() throws Exception {
-        try {
-            unplugDevice();
-            Thread.sleep(WAIT_TIME_SHORT);
-            enableBinderStats();
-            binderStatsNoSampling();
-            resetBinderStats();
-            StatsdConfig.Builder config = createConfigBuilder();
-            addGaugeAtomWithDimensions(config, Atom.BINDER_CALLS_FIELD_NUMBER, null);
-
-            uploadConfig(config);
-            Thread.sleep(WAIT_TIME_SHORT);
-
-            runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification",3_000);
-
-            setAppBreadcrumbPredicate();
-            Thread.sleep(WAIT_TIME_SHORT);
-
-            boolean found = false;
-            int uid = getUid();
-            List<Atom> atomList = getGaugeMetricDataList();
-            for (Atom atom : atomList) {
-                BinderCalls calls = atom.getBinderCalls();
-                boolean classMatches = calls.getServiceClassName().contains(
-                        "com.android.server.notification.NotificationManagerService");
-                boolean methodMatches = calls.getServiceMethodName()
-                        .equals("createNotificationChannels");
-
-                if (calls.getUid() == uid && classMatches && methodMatches) {
-                    found = true;
-                    assertThat(calls.getRecordedCallCount()).isGreaterThan(0L);
-                    assertThat(calls.getCallCount()).isGreaterThan(0L);
-                    assertThat(calls.getRecordedTotalLatencyMicros())
-                        .isIn(Range.open(0L, 1000000L));
-                    assertThat(calls.getRecordedTotalCpuMicros()).isIn(Range.open(0L, 1000000L));
-                }
-            }
-
-            assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
-                .that(found).isTrue();
-
-        } finally {
-            disableBinderStats();
-            plugInAc();
-        }
-    }
-
-    public void testLooperStats() throws Exception {
-        try {
-            unplugDevice();
-            setUpLooperStats();
-            StatsdConfig.Builder config = createConfigBuilder();
-            addGaugeAtomWithDimensions(config, Atom.LOOPER_STATS_FIELD_NUMBER, null);
-            uploadConfig(config);
-            Thread.sleep(WAIT_TIME_SHORT);
-
-            runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification", 3_000);
-
-            setAppBreadcrumbPredicate();
-            Thread.sleep(WAIT_TIME_SHORT);
-
-            List<Atom> atomList = getGaugeMetricDataList();
-
-            boolean found = false;
-            int uid = getUid();
-            for (Atom atom : atomList) {
-                LooperStats stats = atom.getLooperStats();
-                String notificationServiceFullName =
-                        "com.android.server.notification.NotificationManagerService";
-                boolean handlerMatches =
-                        stats.getHandlerClassName().equals(
-                                notificationServiceFullName + "$WorkerHandler");
-                boolean messageMatches =
-                        stats.getMessageName().equals(
-                                notificationServiceFullName + "$EnqueueNotificationRunnable");
-                if (atom.getLooperStats().getUid() == uid && handlerMatches && messageMatches) {
-                    found = true;
-                    assertThat(stats.getMessageCount()).isGreaterThan(0L);
-                    assertThat(stats.getRecordedMessageCount()).isGreaterThan(0L);
-                    assertThat(stats.getRecordedTotalLatencyMicros())
-                        .isIn(Range.open(0L, 1000000L));
-                    assertThat(stats.getRecordedTotalCpuMicros()).isIn(Range.open(0L, 1000000L));
-                    assertThat(stats.getRecordedMaxLatencyMicros()).isIn(Range.open(0L, 1000000L));
-                    assertThat(stats.getRecordedMaxCpuMicros()).isIn(Range.open(0L, 1000000L));
-                    assertThat(stats.getRecordedDelayMessageCount()).isGreaterThan(0L);
-                    assertThat(stats.getRecordedTotalDelayMillis())
-                        .isIn(Range.closedOpen(0L, 5000L));
-                    assertThat(stats.getRecordedMaxDelayMillis()).isIn(Range.closedOpen(0L, 5000L));
-                }
-            }
-            assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
-                .that(found).isTrue();
-        } finally {
-            cleanUpLooperStats();
-            plugInAc();
-        }
-    }
-
-    public void testProcessMemoryState() throws Exception {
-        // Get ProcessMemoryState as a simple gauge metric.
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER, null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Start test app.
-        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
-                "action.show_notification")) {
-            Thread.sleep(WAIT_TIME_LONG);
-            // Trigger a pull and wait for new pull before killing the process.
-            setAppBreadcrumbPredicate();
-            Thread.sleep(WAIT_TIME_LONG);
-        }
-
-        // Assert about ProcessMemoryState for the test app.
-        List<Atom> atoms = getGaugeMetricDataList();
-        int uid = getUid();
-        boolean found = false;
-        for (Atom atom : atoms) {
-            ProcessMemoryState state = atom.getProcessMemoryState();
-            if (state.getUid() != uid) {
-                continue;
-            }
-            found = true;
-            assertThat(state.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
-            assertThat(state.getOomAdjScore()).isAtLeast(0);
-            assertThat(state.getPageFault()).isAtLeast(0L);
-            assertThat(state.getPageMajorFault()).isAtLeast(0L);
-            assertThat(state.getRssInBytes()).isGreaterThan(0L);
-            assertThat(state.getCacheInBytes()).isAtLeast(0L);
-            assertThat(state.getSwapInBytes()).isAtLeast(0L);
-        }
-        assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
-            .that(found).isTrue();
-    }
-
-    public void testProcessMemoryHighWaterMark() throws Exception {
-        // Get ProcessMemoryHighWaterMark as a simple gauge metric.
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_HIGH_WATER_MARK_FIELD_NUMBER, null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Start test app and trigger a pull while it is running.
-        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
-                "action.show_notification")) {
-            Thread.sleep(WAIT_TIME_SHORT);
-            // Trigger a pull and wait for new pull before killing the process.
-            setAppBreadcrumbPredicate();
-            Thread.sleep(WAIT_TIME_LONG);
-        }
-
-        // Assert about ProcessMemoryHighWaterMark for the test app, statsd and system server.
-        List<Atom> atoms = getGaugeMetricDataList();
-        int uid = getUid();
-        boolean foundTestApp = false;
-        boolean foundStatsd = false;
-        boolean foundSystemServer = false;
-        for (Atom atom : atoms) {
-            ProcessMemoryHighWaterMark state = atom.getProcessMemoryHighWaterMark();
-            if (state.getUid() == uid) {
-                foundTestApp = true;
-                assertThat(state.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
-                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
-            } else if (state.getProcessName().contains("/statsd")) {
-                foundStatsd = true;
-                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
-            } else if (state.getProcessName().equals("system")) {
-                foundSystemServer = true;
-                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
-            }
-        }
-        assertWithMessage(String.format("Did not find a matching atom for test app uid=%d",uid))
-            .that(foundTestApp).isTrue();
-        assertWithMessage("Did not find a matching atom for statsd").that(foundStatsd).isTrue();
-        assertWithMessage("Did not find a matching atom for system server")
-            .that(foundSystemServer).isTrue();
-    }
-
-    public void testProcessMemorySnapshot() throws Exception {
-        // Get ProcessMemorySnapshot as a simple gauge metric.
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER, null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Start test app and trigger a pull while it is running.
-        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
-                "action.show_notification")) {
-            Thread.sleep(WAIT_TIME_LONG);
-            setAppBreadcrumbPredicate();
-        }
-
-        // Assert about ProcessMemorySnapshot for the test app, statsd and system server.
-        List<Atom> atoms = getGaugeMetricDataList();
-        int uid = getUid();
-        boolean foundTestApp = false;
-        boolean foundStatsd = false;
-        boolean foundSystemServer = false;
-        for (Atom atom : atoms) {
-          ProcessMemorySnapshot snapshot = atom.getProcessMemorySnapshot();
-          if (snapshot.getUid() == uid) {
-              foundTestApp = true;
-              assertThat(snapshot.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
-          } else if (snapshot.getProcessName().contains("/statsd")) {
-              foundStatsd = true;
-          } else if (snapshot.getProcessName().equals("system")) {
-              foundSystemServer = true;
-          }
-
-          assertThat(snapshot.getPid()).isGreaterThan(0);
-          assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isAtLeast(0);
-          assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isEqualTo(
-                  snapshot.getAnonRssInKilobytes() + snapshot.getSwapInKilobytes());
-          assertThat(snapshot.getRssInKilobytes()).isAtLeast(0);
-          assertThat(snapshot.getAnonRssInKilobytes()).isAtLeast(0);
-          assertThat(snapshot.getSwapInKilobytes()).isAtLeast(0);
-        }
-        assertWithMessage(String.format("Did not find a matching atom for test app uid=%d",uid))
-            .that(foundTestApp).isTrue();
-        assertWithMessage("Did not find a matching atom for statsd").that(foundStatsd).isTrue();
-        assertWithMessage("Did not find a matching atom for system server")
-            .that(foundSystemServer).isTrue();
-    }
-
-    public void testIonHeapSize_optional() throws Exception {
-        if (isIonHeapSizeMandatory()) {
-            return;
-        }
-
-        List<Atom> atoms = pullIonHeapSizeAsGaugeMetric();
-        if (atoms.isEmpty()) {
-            // No support.
-            return;
-        }
-        assertIonHeapSize(atoms);
-    }
-
-    public void testIonHeapSize_mandatory() throws Exception {
-        if (!isIonHeapSizeMandatory()) {
-            return;
-        }
-
-        List<Atom> atoms = pullIonHeapSizeAsGaugeMetric();
-        assertIonHeapSize(atoms);
-    }
-
-    /** Returns whether IonHeapSize atom is supported. */
-    private boolean isIonHeapSizeMandatory() throws Exception {
-        // Support is guaranteed by libmeminfo VTS.
-        return PropertyUtil.getFirstApiLevel(getDevice()) >= 30;
-    }
-
-    /** Returns IonHeapSize atoms pulled as a simple gauge metric while test app is running. */
-    private List<Atom> pullIonHeapSizeAsGaugeMetric() throws Exception {
-        // Get IonHeapSize as a simple gauge metric.
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.ION_HEAP_SIZE_FIELD_NUMBER, null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Start test app and trigger a pull while it is running.
-        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
-                "action.show_notification")) {
-            setAppBreadcrumbPredicate();
-            Thread.sleep(WAIT_TIME_LONG);
-        }
-
-        return getGaugeMetricDataList();
-    }
-
-    private static void assertIonHeapSize(List<Atom> atoms) {
-        assertThat(atoms).hasSize(1);
-        IonHeapSize ionHeapSize = atoms.get(0).getIonHeapSize();
-        assertThat(ionHeapSize.getTotalSizeKb()).isAtLeast(0);
-    }
-
-    /**
-     * The the app id from a uid.
-     *
-     * @param uid The uid of the app
-     *
-     * @return The app id of the app
-     *
-     * @see android.os.UserHandle#getAppId
-     */
-    private static int getAppId(int uid) {
-        return uid % 100000;
-    }
-
-    public void testRoleHolder() throws Exception {
-        // Make device side test package a role holder
-        String callScreenAppRole = "android.app.role.CALL_SCREENING";
-        getDevice().executeShellCommand(
-                "cmd role add-role-holder " + callScreenAppRole + " " + DEVICE_SIDE_TEST_PACKAGE);
-
-        // Set up what to collect
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.ROLE_HOLDER_FIELD_NUMBER, null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        boolean verifiedKnowRoleState = false;
-
-        // Pull a report
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        int testAppId = getAppId(getUid());
-
-        for (Atom atom : getGaugeMetricDataList()) {
-            AtomsProto.RoleHolder roleHolder = atom.getRoleHolder();
-
-            assertThat(roleHolder.getPackageName()).isNotNull();
-            assertThat(roleHolder.getUid()).isAtLeast(0);
-            assertThat(roleHolder.getRole()).isNotNull();
-
-            if (roleHolder.getPackageName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
-                assertThat(getAppId(roleHolder.getUid())).isEqualTo(testAppId);
-                assertThat(roleHolder.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
-                assertThat(roleHolder.getRole()).isEqualTo(callScreenAppRole);
-
-                verifiedKnowRoleState = true;
-            }
-        }
-
-        assertThat(verifiedKnowRoleState).isTrue();
-    }
-
-    public void testDangerousPermissionState() throws Exception {
-        final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED =  1 << 8;
-        final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED =  1 << 9;
-
-        // Set up what to collect
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER, null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        boolean verifiedKnowPermissionState = false;
-
-        // Pull a report
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        int testAppId = getAppId(getUid());
-
-        for (Atom atom : getGaugeMetricDataList()) {
-            DangerousPermissionState permissionState = atom.getDangerousPermissionState();
-
-            assertThat(permissionState.getPermissionName()).isNotNull();
-            assertThat(permissionState.getUid()).isAtLeast(0);
-            assertThat(permissionState.getPackageName()).isNotNull();
-
-            if (getAppId(permissionState.getUid()) == testAppId) {
-
-                if (permissionState.getPermissionName().contains(
-                        "ACCESS_FINE_LOCATION")) {
-                    assertThat(permissionState.getIsGranted()).isTrue();
-                    assertThat(permissionState.getPermissionFlags() & ~(
-                            FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
-                            | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED))
-                        .isEqualTo(0);
-
-                    verifiedKnowPermissionState = true;
-                }
-            }
-        }
-
-        assertThat(verifiedKnowPermissionState).isTrue();
-    }
-
-    public void testDangerousPermissionStateSampled() throws Exception {
-        // get full atom for reference
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER, null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        List<DangerousPermissionState> fullDangerousPermissionState = new ArrayList<>();
-        for (Atom atom : getGaugeMetricDataList()) {
-            fullDangerousPermissionState.add(atom.getDangerousPermissionState());
-        }
-
-        removeConfig(CONFIG_ID);
-        getReportList(); // Clears data.
-        List<Atom> gaugeMetricDataList = null;
-
-        // retries in case sampling returns full list or empty list - which should be extremely rare
-        for (int attempt = 0; attempt < 10; attempt++) {
-            // Set up what to collect
-            config = createConfigBuilder();
-            addGaugeAtomWithDimensions(config, Atom.DANGEROUS_PERMISSION_STATE_SAMPLED_FIELD_NUMBER,
-                    null);
-            uploadConfig(config);
-            Thread.sleep(WAIT_TIME_SHORT);
-
-            // Pull a report
-            setAppBreadcrumbPredicate();
-            Thread.sleep(WAIT_TIME_SHORT);
-
-            gaugeMetricDataList = getGaugeMetricDataList();
-            if (gaugeMetricDataList.size() > 0
-                    && gaugeMetricDataList.size() < fullDangerousPermissionState.size()) {
-                break;
-            }
-            removeConfig(CONFIG_ID);
-            getReportList(); // Clears data.
-        }
-        assertThat(gaugeMetricDataList.size()).isGreaterThan(0);
-        assertThat(gaugeMetricDataList.size()).isLessThan(fullDangerousPermissionState.size());
-
-        long lastUid = -1;
-        int fullIndex = 0;
-
-        for (Atom atom : getGaugeMetricDataList()) {
-            DangerousPermissionStateSampled permissionState =
-                    atom.getDangerousPermissionStateSampled();
-
-            DangerousPermissionState referenceState = fullDangerousPermissionState.get(fullIndex);
-
-            if (referenceState.getUid() != permissionState.getUid()) {
-                // atoms are sampled on uid basis if uid is present, all related permissions must
-                // be logged.
-                assertThat(permissionState.getUid()).isNotEqualTo(lastUid);
-                continue;
-            }
-
-            lastUid = permissionState.getUid();
-
-            assertThat(permissionState.getPermissionFlags()).isEqualTo(
-                    referenceState.getPermissionFlags());
-            assertThat(permissionState.getIsGranted()).isEqualTo(referenceState.getIsGranted());
-            assertThat(permissionState.getPermissionName()).isEqualTo(
-                    referenceState.getPermissionName());
-
-            fullIndex++;
-        }
-    }
-
-    public void testAppOps() throws Exception {
-        // Set up what to collect
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.APP_OPS_FIELD_NUMBER, null);
-        uploadConfig(config);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testAppOps");
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Pull a report
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        ArrayList<Integer> expectedOps = new ArrayList<>();
-        for (int i = 0; i < NUM_APP_OPS; i++) {
-            expectedOps.add(i);
-        }
-
-        for (Descriptors.EnumValueDescriptor valueDescriptor :
-                AttributedAppOps.getDefaultInstance().getOp().getDescriptorForType().getValues()) {
-            if (valueDescriptor.getOptions().hasDeprecated()) {
-                // Deprecated app op, remove from list of expected ones.
-                expectedOps.remove(expectedOps.indexOf(valueDescriptor.getNumber()));
-            }
-        }
-        for (Atom atom : getGaugeMetricDataList()) {
-
-            AppOps appOps = atom.getAppOps();
-            if (appOps.getPackageName().equals(TEST_PACKAGE_NAME)) {
-                if (appOps.getOpId().getNumber() == -1) {
-                    continue;
-                }
-                long totalNoted = appOps.getTrustedForegroundGrantedCount()
-                        + appOps.getTrustedBackgroundGrantedCount()
-                        + appOps.getTrustedForegroundRejectedCount()
-                        + appOps.getTrustedBackgroundRejectedCount();
-                assertWithMessage("Operation in APP_OPS_ENUM_MAP: " + appOps.getOpId().getNumber())
-                        .that(totalNoted - 1).isEqualTo(appOps.getOpId().getNumber());
-                assertWithMessage("Unexpected Op reported").that(expectedOps).contains(
-                        appOps.getOpId().getNumber());
-                expectedOps.remove(expectedOps.indexOf(appOps.getOpId().getNumber()));
-            }
-        }
-        assertWithMessage("Logging app op ids are missing in report.").that(expectedOps).isEmpty();
-    }
-
-    public void testANROccurred() throws Exception {
-        final int atomTag = Atom.ANR_OCCURRED_FIELD_NUMBER;
-        createAndUploadConfig(atomTag, false);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        try (AutoCloseable a = withActivity("ANRActivity", null, null)) {
-            Thread.sleep(WAIT_TIME_SHORT);
-            getDevice().executeShellCommand(
-                    "am broadcast -a action_anr -p " + DEVICE_SIDE_TEST_PACKAGE);
-            Thread.sleep(20_000);
-        }
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        assertThat(data).hasSize(1);
-        assertThat(data.get(0).getAtom().hasAnrOccurred()).isTrue();
-        ANROccurred atom = data.get(0).getAtom().getAnrOccurred();
-        assertThat(atom.getIsInstantApp().getNumber())
-            .isEqualTo(ANROccurred.InstantApp.FALSE_VALUE);
-        assertThat(atom.getForegroundState().getNumber())
-            .isEqualTo(ANROccurred.ForegroundState.FOREGROUND_VALUE);
-        assertThat(atom.getErrorSource()).isEqualTo(ErrorSource.DATA_APP);
-        assertThat(atom.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
-    }
-
-    public void testWriteRawTestAtom() throws Exception {
-        final int atomTag = Atom.TEST_ATOM_REPORTED_FIELD_NUMBER;
-        createAndUploadConfig(atomTag, true);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWriteRawTestAtom");
-
-        Thread.sleep(WAIT_TIME_SHORT);
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-        assertThat(data).hasSize(4);
-
-        TestAtomReported atom = data.get(0).getAtom().getTestAtomReported();
-        List<AttributionNode> attrChain = atom.getAttributionNodeList();
-        assertThat(attrChain).hasSize(2);
-        assertThat(attrChain.get(0).getUid()).isEqualTo(1234);
-        assertThat(attrChain.get(0).getTag()).isEqualTo("tag1");
-        assertThat(attrChain.get(1).getUid()).isEqualTo(getUid());
-        assertThat(attrChain.get(1).getTag()).isEqualTo("tag2");
-
-        assertThat(atom.getIntField()).isEqualTo(42);
-        assertThat(atom.getLongField()).isEqualTo(Long.MAX_VALUE);
-        assertThat(atom.getFloatField()).isEqualTo(3.14f);
-        assertThat(atom.getStringField()).isEqualTo("This is a basic test!");
-        assertThat(atom.getBooleanField()).isFalse();
-        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.ON_VALUE);
-        assertThat(atom.getBytesField().getExperimentIdList())
-            .containsExactly(1L, 2L, 3L).inOrder();
-
-
-        atom = data.get(1).getAtom().getTestAtomReported();
-        attrChain = atom.getAttributionNodeList();
-        assertThat(attrChain).hasSize(2);
-        assertThat(attrChain.get(0).getUid()).isEqualTo(9999);
-        assertThat(attrChain.get(0).getTag()).isEqualTo("tag9999");
-        assertThat(attrChain.get(1).getUid()).isEqualTo(getUid());
-        assertThat(attrChain.get(1).getTag()).isEmpty();
-
-        assertThat(atom.getIntField()).isEqualTo(100);
-        assertThat(atom.getLongField()).isEqualTo(Long.MIN_VALUE);
-        assertThat(atom.getFloatField()).isEqualTo(-2.5f);
-        assertThat(atom.getStringField()).isEqualTo("Test null uid");
-        assertThat(atom.getBooleanField()).isTrue();
-        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.UNKNOWN_VALUE);
-        assertThat(atom.getBytesField().getExperimentIdList())
-            .containsExactly(1L, 2L, 3L).inOrder();
-
-        atom = data.get(2).getAtom().getTestAtomReported();
-        attrChain = atom.getAttributionNodeList();
-        assertThat(attrChain).hasSize(1);
-        assertThat(attrChain.get(0).getUid()).isEqualTo(getUid());
-        assertThat(attrChain.get(0).getTag()).isEqualTo("tag1");
-
-        assertThat(atom.getIntField()).isEqualTo(-256);
-        assertThat(atom.getLongField()).isEqualTo(-1234567890L);
-        assertThat(atom.getFloatField()).isEqualTo(42.01f);
-        assertThat(atom.getStringField()).isEqualTo("Test non chained");
-        assertThat(atom.getBooleanField()).isTrue();
-        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
-        assertThat(atom.getBytesField().getExperimentIdList())
-            .containsExactly(1L, 2L, 3L).inOrder();
-
-        atom = data.get(3).getAtom().getTestAtomReported();
-        attrChain = atom.getAttributionNodeList();
-        assertThat(attrChain).hasSize(1);
-        assertThat(attrChain.get(0).getUid()).isEqualTo(getUid());
-        assertThat(attrChain.get(0).getTag()).isEmpty();
-
-        assertThat(atom.getIntField()).isEqualTo(0);
-        assertThat(atom.getLongField()).isEqualTo(0L);
-        assertThat(atom.getFloatField()).isEqualTo(0f);
-        assertThat(atom.getStringField()).isEmpty();
-        assertThat(atom.getBooleanField()).isTrue();
-        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
-        assertThat(atom.getBytesField().getExperimentIdList()).isEmpty();
-    }
-
-    public void testNotificationPackagePreferenceExtraction() throws Exception {
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config,
-                    Atom.PACKAGE_NOTIFICATION_PREFERENCES_FIELD_NUMBER,
-                    null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-        runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification");
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        List<PackageNotificationPreferences> allPreferences = new ArrayList<>();
-        for (Atom atom : getGaugeMetricDataList()){
-            if(atom.hasPackageNotificationPreferences()) {
-                allPreferences.add(atom.getPackageNotificationPreferences());
-            }
-        }
-        assertThat(allPreferences.size()).isGreaterThan(0);
-
-        boolean foundTestPackagePreferences = false;
-        int uid = getUid();
-        for (PackageNotificationPreferences pref : allPreferences) {
-            assertThat(pref.getUid()).isGreaterThan(0);
-            assertTrue(pref.hasImportance());
-            assertTrue(pref.hasVisibility());
-            assertTrue(pref.hasUserLockedFields());
-            if(pref.getUid() == uid){
-                assertThat(pref.getImportance()).isEqualTo(-1000);  //UNSPECIFIED_IMPORTANCE
-                assertThat(pref.getVisibility()).isEqualTo(-1000);  //UNSPECIFIED_VISIBILITY
-                foundTestPackagePreferences = true;
-            }
-        }
-        assertTrue(foundTestPackagePreferences);
-    }
-
-    public void testNotificationChannelPreferencesExtraction() throws Exception {
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config,
-                    Atom.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES_FIELD_NUMBER,
-                    null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-        runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification");
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        List<PackageNotificationChannelPreferences> allChannelPreferences = new ArrayList<>();
-        for(Atom atom : getGaugeMetricDataList()) {
-            if (atom.hasPackageNotificationChannelPreferences()) {
-               allChannelPreferences.add(atom.getPackageNotificationChannelPreferences());
-            }
-        }
-        assertThat(allChannelPreferences.size()).isGreaterThan(0);
-
-        boolean foundTestPackagePreferences = false;
-        int uid = getUid();
-        for (PackageNotificationChannelPreferences pref : allChannelPreferences) {
-            assertThat(pref.getUid()).isGreaterThan(0);
-            assertTrue(pref.hasChannelId());
-            assertTrue(pref.hasChannelName());
-            assertTrue(pref.hasDescription());
-            assertTrue(pref.hasImportance());
-            assertTrue(pref.hasUserLockedFields());
-            assertTrue(pref.hasIsDeleted());
-            if(uid == pref.getUid() && pref.getChannelId().equals("StatsdCtsChannel")) {
-                assertThat(pref.getChannelName()).isEqualTo("Statsd Cts");
-                assertThat(pref.getDescription()).isEqualTo("Statsd Cts Channel");
-                assertThat(pref.getImportance()).isEqualTo(3);  // IMPORTANCE_DEFAULT
-                foundTestPackagePreferences = true;
-            }
-        }
-        assertTrue(foundTestPackagePreferences);
-    }
-
-    public void testNotificationChannelGroupPreferencesExtraction() throws Exception {
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config,
-                    Atom.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES_FIELD_NUMBER,
-                    null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-        runActivity("StatsdCtsForegroundActivity", "action", "action.create_channel_group");
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        List<PackageNotificationChannelGroupPreferences> allGroupPreferences = new ArrayList<>();
-        for(Atom atom : getGaugeMetricDataList()) {
-            if (atom.hasPackageNotificationChannelGroupPreferences()) {
-                allGroupPreferences.add(atom.getPackageNotificationChannelGroupPreferences());
-            }
-        }
-        assertThat(allGroupPreferences.size()).isGreaterThan(0);
-
-        boolean foundTestPackagePreferences = false;
-        int uid = getUid();
-        for(PackageNotificationChannelGroupPreferences pref : allGroupPreferences) {
-            assertThat(pref.getUid()).isGreaterThan(0);
-            assertTrue(pref.hasGroupId());
-            assertTrue(pref.hasGroupName());
-            assertTrue(pref.hasDescription());
-            assertTrue(pref.hasIsBlocked());
-            assertTrue(pref.hasUserLockedFields());
-            if(uid == pref.getUid() && pref.getGroupId().equals("StatsdCtsGroup")) {
-                assertThat(pref.getGroupName()).isEqualTo("Statsd Cts Group");
-                assertThat(pref.getDescription()).isEqualTo("StatsdCtsGroup Description");
-                assertThat(pref.getIsBlocked()).isFalse();
-                foundTestPackagePreferences = true;
-            }
-        }
-        assertTrue(foundTestPackagePreferences);
-    }
-
-    public void testNotificationReported() throws Exception {
-        StatsdConfig.Builder config = getPulledConfig();
-        addAtomEvent(config, Atom.NOTIFICATION_REPORTED_FIELD_NUMBER,
-            Arrays.asList(createFvm(NotificationReported.PACKAGE_NAME_FIELD_NUMBER)
-                              .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-        runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification");
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-        assertThat(data).hasSize(1);
-        assertThat(data.get(0).getAtom().hasNotificationReported()).isTrue();
-        AtomsProto.NotificationReported n = data.get(0).getAtom().getNotificationReported();
-        assertThat(n.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
-        assertThat(n.getUid()).isEqualTo(getUid());
-        assertThat(n.getNotificationIdHash()).isEqualTo(1);  // smallHash(0x7f080001)
-        assertThat(n.getChannelIdHash()).isEqualTo(SmallHash.hash("StatsdCtsChannel"));
-        assertThat(n.getGroupIdHash()).isEqualTo(0);
-        assertFalse(n.getIsGroupSummary());
-        assertThat(n.getCategory()).isEmpty();
-        assertThat(n.getStyle()).isEqualTo(0);
-        assertThat(n.getNumPeople()).isEqualTo(0);
-    }
-
-    public void testSettingsStatsReported() throws Exception {
-        // Base64 encoded proto com.android.service.nano.StringListParamProto,
-        // which contains two strings "font_scale" and "screen_auto_brightness_adj".
-        final String encoded = "ChpzY3JlZW5fYXV0b19icmlnaHRuZXNzX2FkagoKZm9udF9zY2FsZQ";
-        final String font_scale = "font_scale";
-        SettingSnapshot snapshot = null;
-
-        // Set whitelist through device config.
-        Thread.sleep(WAIT_TIME_SHORT);
-        getDevice().executeShellCommand(
-                "device_config put settings_stats SystemFeature__float_whitelist " + encoded);
-        Thread.sleep(WAIT_TIME_SHORT);
-        // Set font_scale value
-        getDevice().executeShellCommand("settings put system font_scale 1.5");
-
-        // Get SettingSnapshot as a simple gauge metric.
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.SETTING_SNAPSHOT_FIELD_NUMBER, null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Start test app and trigger a pull while it is running.
-        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
-                "action.show_notification")) {
-            Thread.sleep(WAIT_TIME_SHORT);
-            // Trigger a pull and wait for new pull before killing the process.
-            setAppBreadcrumbPredicate();
-            Thread.sleep(WAIT_TIME_LONG);
-        }
-
-        // Test the size of atoms. It should contain at least "font_scale" and
-        // "screen_auto_brightness_adj" two setting values.
-        List<Atom> atoms = getGaugeMetricDataList();
-        assertThat(atoms.size()).isAtLeast(2);
-        for (Atom atom : atoms) {
-            SettingSnapshot settingSnapshot = atom.getSettingSnapshot();
-            if (font_scale.equals(settingSnapshot.getName())) {
-                snapshot = settingSnapshot;
-                break;
-            }
-        }
-
-        Thread.sleep(WAIT_TIME_SHORT);
-        // Test the data of atom.
-        assertNotNull(snapshot);
-        // Get font_scale value and test value type.
-        final float fontScale = Float.parseFloat(
-                getDevice().executeShellCommand("settings get system font_scale"));
-        assertThat(snapshot.getType()).isEqualTo(
-                SettingSnapshot.SettingsValueType.ASSIGNED_FLOAT_TYPE);
-        assertThat(snapshot.getBoolValue()).isEqualTo(false);
-        assertThat(snapshot.getIntValue()).isEqualTo(0);
-        assertThat(snapshot.getFloatValue()).isEqualTo(fontScale);
-        assertThat(snapshot.getStrValue()).isEqualTo("");
-        assertThat(snapshot.getUserId()).isEqualTo(0);
-    }
-
-    public void testIntegrityCheckAtomReportedDuringInstall() throws Exception {
-        createAndUploadConfig(AtomsProto.Atom.INTEGRITY_CHECK_RESULT_REPORTED_FIELD_NUMBER);
-
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        installTestApp();
-
-        List<EventMetricData> data = getEventMetricDataList();
-
-        assertThat(data.size()).isEqualTo(1);
-        assertThat(data.get(0).getAtom().hasIntegrityCheckResultReported()).isTrue();
-        IntegrityCheckResultReported result = data.get(0)
-                .getAtom().getIntegrityCheckResultReported();
-        assertThat(result.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
-        // we do not assert on certificates since it seem to differ by device.
-        assertThat(result.getInstallerPackageName()).isEqualTo("adb");
-        assertThat(result.getVersionCode()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE_VERSION);
-        assertThat(result.getResponse()).isEqualTo(ALLOWED);
-        assertThat(result.getCausedByAppCertRule()).isFalse();
-        assertThat(result.getCausedByInstallerRule()).isFalse();
-    }
-
-    public void testMobileBytesTransfer() throws Throwable {
-        final int appUid = getUid();
-
-        // Verify MobileBytesTransfer, passing a ThrowingPredicate that verifies contents of
-        // corresponding atom type to prevent code duplication. The passed predicate returns
-        // true if the atom of appUid is found, false otherwise, and throws an exception if
-        // contents are not expected.
-        doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_FIELD_NUMBER, (atom) -> {
-            final AtomsProto.MobileBytesTransfer data = ((Atom) atom).getMobileBytesTransfer();
-            if (data.getUid() == appUid) {
-                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
-                        data.getRxPackets(), data.getTxPackets());
-                return true; // found
-            }
-            return false;
-        });
-    }
-
-    public void testMobileBytesTransferByFgBg() throws Throwable {
-        final int appUid = getUid();
-
-        doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_BY_FG_BG_FIELD_NUMBER, (atom) -> {
-            final AtomsProto.MobileBytesTransferByFgBg data =
-                    ((Atom) atom).getMobileBytesTransferByFgBg();
-            if (data.getUid() == appUid && data.getIsForeground()) {
-                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
-                        data.getRxPackets(), data.getTxPackets());
-                return true; // found
-            }
-            return false;
-        });
-    }
-
-    public void testDataUsageBytesTransfer() throws Throwable {
-        final boolean subtypeCombined = getNetworkStatsCombinedSubTypeEnabled();
-
-        doTestMobileBytesTransferThat(Atom.DATA_USAGE_BYTES_TRANSFER_FIELD_NUMBER, (atom) -> {
-            final AtomsProto.DataUsageBytesTransfer data =
-                    ((Atom) atom).getDataUsageBytesTransfer();
-            if (data.getState() == 1 /*NetworkStats.SET_FOREGROUND*/) {
-                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
-                        data.getRxPackets(), data.getTxPackets());
-                // TODO: verify the RAT type field with the value gotten from device.
-                if (subtypeCombined) {
-                    assertThat(data.getRatType()).isEqualTo(
-                            NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
-                } else {
-                    assertThat(data.getRatType()).isGreaterThan(
-                            NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
-                }
-
-                // Assert that subscription info is valid.
-                assertThat(data.getSimMcc()).matches("^\\d{3}$");
-                assertThat(data.getSimMnc()).matches("^\\d{2,3}$");
-                assertThat(data.getCarrierId()).isNotEqualTo(
-                        -1); // TelephonyManager#UNKNOWN_CARRIER_ID
-
-                return true; // found
-            }
-            return false;
-        });
-    }
-
-    // TODO(b/157651730): Determine how to test tag and metered state within atom.
-    public void testBytesTransferByTagAndMetered() throws Throwable {
-        final int appUid = getUid();
-        final int atomId = Atom.BYTES_TRANSFER_BY_TAG_AND_METERED_FIELD_NUMBER;
-
-        doTestMobileBytesTransferThat(atomId, (atom) -> {
-            final AtomsProto.BytesTransferByTagAndMetered data =
-                    ((Atom) atom).getBytesTransferByTagAndMetered();
-            if (data.getUid() == appUid && data.getTag() == 0 /*app traffic generated on tag 0*/) {
-                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
-                        data.getRxPackets(), data.getTxPackets());
-                return true; // found
-            }
-            return false;
-        });
-    }
-
-    public void testIsolatedToHostUidMapping() throws Exception {
-        createAndUploadConfig(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, /*useAttribution=*/false);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Create an isolated service from which An AppBreadcrumbReported atom is written.
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testIsolatedProcessService");
-
-        List<EventMetricData> data = getEventMetricDataList();
-        assertThat(data).hasSize(1);
-        AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
-        assertThat(atom.getUid()).isEqualTo(getUid());
-        assertThat(atom.getLabel()).isEqualTo(0);
-        assertThat(atom.getState()).isEqualTo(AppBreadcrumbReported.State.START);
-    }
-
-    public void testPushedBlobStoreStats() throws Exception {
-        StatsdConfig.Builder conf = createConfigBuilder();
-        addAtomEvent(conf, Atom.BLOB_COMMITTED_FIELD_NUMBER, false);
-        addAtomEvent(conf, Atom.BLOB_LEASED_FIELD_NUMBER, false);
-        addAtomEvent(conf, Atom.BLOB_OPENED_FIELD_NUMBER, false);
-        uploadConfig(conf);
-
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testBlobStore");
-
-        List<EventMetricData> data = getEventMetricDataList();
-        assertThat(data).hasSize(3);
-
-        BlobCommitted blobCommitted = data.get(0).getAtom().getBlobCommitted();
-        final long blobId = blobCommitted.getBlobId();
-        final long blobSize = blobCommitted.getSize();
-        assertThat(blobCommitted.getUid()).isEqualTo(getUid());
-        assertThat(blobId).isNotEqualTo(0);
-        assertThat(blobSize).isNotEqualTo(0);
-        assertThat(blobCommitted.getResult()).isEqualTo(BlobCommitted.Result.SUCCESS);
-
-        BlobLeased blobLeased = data.get(1).getAtom().getBlobLeased();
-        assertThat(blobLeased.getUid()).isEqualTo(getUid());
-        assertThat(blobLeased.getBlobId()).isEqualTo(blobId);
-        assertThat(blobLeased.getSize()).isEqualTo(blobSize);
-        assertThat(blobLeased.getResult()).isEqualTo(BlobLeased.Result.SUCCESS);
-
-        BlobOpened blobOpened = data.get(2).getAtom().getBlobOpened();
-        assertThat(blobOpened.getUid()).isEqualTo(getUid());
-        assertThat(blobOpened.getBlobId()).isEqualTo(blobId);
-        assertThat(blobOpened.getSize()).isEqualTo(blobSize);
-        assertThat(blobOpened.getResult()).isEqualTo(BlobOpened.Result.SUCCESS);
-    }
-
-    // Constants that match the constants for AtomTests#testBlobStore
-    private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
-    private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
-    private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
-    private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
-
-    public void testPulledBlobStoreStats() throws Exception {
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config,
-                Atom.BLOB_INFO_FIELD_NUMBER,
-                null);
-        uploadConfig(config);
-
-        final long testStartTimeMs = System.currentTimeMillis();
-        Thread.sleep(WAIT_TIME_SHORT);
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testBlobStore");
-        Thread.sleep(WAIT_TIME_LONG);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Add commit callback time to test end time to account for async execution
-        final long testEndTimeMs =
-                System.currentTimeMillis() + BLOB_COMMIT_CALLBACK_TIMEOUT_SEC * 1000;
-
-        // Find the BlobInfo for the blob created in the test run
-        AtomsProto.BlobInfo blobInfo = null;
-        for (Atom atom : getGaugeMetricDataList()) {
-            if (atom.hasBlobInfo()) {
-                final AtomsProto.BlobInfo temp = atom.getBlobInfo();
-                if (temp.getCommitters().getCommitter(0).getUid() == getUid()) {
-                    blobInfo = temp;
-                    break;
-                }
-            }
-        }
-        assertThat(blobInfo).isNotNull();
-
-        assertThat(blobInfo.getSize()).isEqualTo(BLOB_FILE_SIZE_BYTES);
-
-        // Check that expiry time is reasonable
-        assertThat(blobInfo.getExpiryTimestampMillis()).isGreaterThan(
-                testStartTimeMs + BLOB_EXPIRY_DURATION_MS);
-        assertThat(blobInfo.getExpiryTimestampMillis()).isLessThan(
-                testEndTimeMs + BLOB_EXPIRY_DURATION_MS);
-
-        // Check that commit time is reasonable
-        final long commitTimeMs = blobInfo.getCommitters().getCommitter(
-                0).getCommitTimestampMillis();
-        assertThat(commitTimeMs).isGreaterThan(testStartTimeMs);
-        assertThat(commitTimeMs).isLessThan(testEndTimeMs);
-
-        // Check that WHITELIST and PRIVATE access mode flags are set
-        assertThat(blobInfo.getCommitters().getCommitter(0).getAccessMode()).isEqualTo(0b1001);
-        assertThat(blobInfo.getCommitters().getCommitter(0).getNumWhitelistedPackage()).isEqualTo(
-                1);
-
-        assertThat(blobInfo.getLeasees().getLeaseeCount()).isGreaterThan(0);
-        assertThat(blobInfo.getLeasees().getLeasee(0).getUid()).isEqualTo(getUid());
-
-        // Check that lease expiry time is reasonable
-        final long leaseExpiryMs = blobInfo.getLeasees().getLeasee(
-                0).getLeaseExpiryTimestampMillis();
-        assertThat(leaseExpiryMs).isGreaterThan(testStartTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
-        assertThat(leaseExpiryMs).isLessThan(testEndTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
-    }
-
-    private void assertDataUsageAtomDataExpected(long rxb, long txb, long rxp, long txp) {
-        assertThat(rxb).isGreaterThan(0L);
-        assertThat(txb).isGreaterThan(0L);
-        assertThat(rxp).isGreaterThan(0L);
-        assertThat(txp).isGreaterThan(0L);
-    }
-
-    private void doTestMobileBytesTransferThat(int atomTag, ThrowingPredicate p)
-            throws Throwable {
-        if (!hasFeature(FEATURE_TELEPHONY, true)) return;
-
-        // Get MobileBytesTransfer as a simple gauge metric.
-        final StatsdConfig.Builder config = getPulledConfig();
-        addGaugeAtomWithDimensions(config, atomTag, null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Generate some traffic on mobile network.
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testGenerateMobileTraffic");
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Force polling NetworkStatsService to get most updated network stats from lower layer.
-        runActivity("StatsdCtsForegroundActivity", "action", "action.poll_network_stats");
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Pull a report
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        final List<Atom> atoms = getGaugeMetricDataList(/*checkTimestampTruncated=*/true);
-        assertThat(atoms.size()).isAtLeast(1);
-
-        boolean foundAppStats = false;
-        for (final Atom atom : atoms) {
-            if (p.accept(atom)) {
-                foundAppStats = true;
-            }
-        }
-        assertWithMessage("uid " + getUid() + " is not found in " + atoms.size() + " atoms")
-                .that(foundAppStats).isTrue();
-    }
-
-    @FunctionalInterface
-    private interface ThrowingPredicate<S, T extends Throwable> {
-        boolean accept(S s) throws T;
-    }
-
-    public void testPackageInstallerV2MetricsReported() throws Throwable {
-        if (!hasFeature(FEATURE_INCREMENTAL_DELIVERY, true)) return;
-        final AtomsProto.PackageInstallerV2Reported report = installPackageUsingV2AndGetReport(
-                new String[]{TEST_INSTALL_APK});
-        assertTrue(report.getIsIncremental());
-        // tests are ran using SHELL_UID and installation will be treated as adb install
-        assertEquals("", report.getPackageName());
-        assertEquals(1, report.getReturnCode());
-        assertTrue(report.getDurationMillis() > 0);
-        assertEquals(getTestFileSize(TEST_INSTALL_APK), report.getApksSizeBytes());
-
-        getDevice().uninstallPackage(TEST_INSTALL_PACKAGE);
-    }
-
-    public void testPackageInstallerV2MetricsReportedForSplits() throws Throwable {
-        if (!hasFeature(FEATURE_INCREMENTAL_DELIVERY, true)) return;
-
-        final AtomsProto.PackageInstallerV2Reported report = installPackageUsingV2AndGetReport(
-                new String[]{TEST_INSTALL_APK_BASE, TEST_INSTALL_APK_SPLIT});
-        assertTrue(report.getIsIncremental());
-        // tests are ran using SHELL_UID and installation will be treated as adb install
-        assertEquals("", report.getPackageName());
-        assertEquals(1, report.getReturnCode());
-        assertTrue(report.getDurationMillis() > 0);
-        assertEquals(
-                getTestFileSize(TEST_INSTALL_APK_BASE) + getTestFileSize(TEST_INSTALL_APK_SPLIT),
-                report.getApksSizeBytes());
-
-        getDevice().uninstallPackage(TEST_INSTALL_PACKAGE);
-    }
-
-    public void testAppForegroundBackground() throws Exception {
-        Set<Integer> onStates = new HashSet<>(Arrays.asList(
-                AppUsageEventOccurred.EventType.MOVE_TO_FOREGROUND_VALUE));
-        Set<Integer> offStates = new HashSet<>(Arrays.asList(
-                AppUsageEventOccurred.EventType.MOVE_TO_BACKGROUND_VALUE));
-
-        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
-        createAndUploadConfig(Atom.APP_USAGE_EVENT_OCCURRED_FIELD_NUMBER, false);
-        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
-
-        getDevice().executeShellCommand(String.format(
-                "am start -n '%s' -e %s %s",
-                "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity",
-                "action", ACTION_SHOW_APPLICATION_OVERLAY));
-        final int waitTime = EXTRA_WAIT_TIME_MS + 5_000; // Overlay may need to sit there a while.
-        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        Function<Atom, Integer> appUsageStateFunction =
-                atom -> atom.getAppUsageEventOccurred().getEventType().getNumber();
-        popUntilFind(data, onStates, appUsageStateFunction); // clear out initial appusage states.s
-        assertStatesOccurred(stateSet, data, 0, appUsageStateFunction);
-    }
-
-    public void testAppForceStopUsageEvent() throws Exception {
-        Set<Integer> onStates = new HashSet<>(Arrays.asList(
-                AppUsageEventOccurred.EventType.MOVE_TO_FOREGROUND_VALUE));
-        Set<Integer> offStates = new HashSet<>(Arrays.asList(
-                AppUsageEventOccurred.EventType.MOVE_TO_BACKGROUND_VALUE));
-
-        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
-        createAndUploadConfig(Atom.APP_USAGE_EVENT_OCCURRED_FIELD_NUMBER, false);
-        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
-
-        getDevice().executeShellCommand(String.format(
-                "am start -n '%s' -e %s %s",
-                "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity",
-                "action", ACTION_LONG_SLEEP_WHILE_TOP));
-        final int waitTime = EXTRA_WAIT_TIME_MS + 5_000;
-        Thread.sleep(waitTime);
-
-        getDevice().executeShellCommand(String.format(
-                "am force-stop %s",
-                "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity"));
-        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        Function<Atom, Integer> appUsageStateFunction =
-                atom -> atom.getAppUsageEventOccurred().getEventType().getNumber();
-        popUntilFind(data, onStates, appUsageStateFunction); // clear out initial appusage states.
-        assertStatesOccurred(stateSet, data, 0, appUsageStateFunction);
-    }
-
-    private AtomsProto.PackageInstallerV2Reported installPackageUsingV2AndGetReport(
-            String[] apkNames) throws Exception {
-        createAndUploadConfig(Atom.PACKAGE_INSTALLER_V2_REPORTED_FIELD_NUMBER);
-        Thread.sleep(WAIT_TIME_SHORT);
-        installPackageUsingIncremental(apkNames, TEST_REMOTE_DIR);
-        assertTrue(getDevice().isPackageInstalled(TEST_INSTALL_PACKAGE));
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        List<AtomsProto.PackageInstallerV2Reported> reports = new ArrayList<>();
-        for(EventMetricData data : getEventMetricDataList()) {
-            if (data.getAtom().hasPackageInstallerV2Reported()) {
-                reports.add(data.getAtom().getPackageInstallerV2Reported());
-            }
-        }
-        assertEquals(1, reports.size());
-        return reports.get(0);
-    }
-
-    private void installPackageUsingIncremental(String[] apkNames, String remoteDirPath)
-            throws Exception {
-        getDevice().executeShellCommand("mkdir " + remoteDirPath);
-        String[] remoteApkPaths = new String[apkNames.length];
-        for (int i = 0; i < remoteApkPaths.length; i++) {
-            remoteApkPaths[i] = pushApkToRemote(apkNames[i], remoteDirPath);
-        }
-        getDevice().executeShellCommand(
-                "pm install-incremental -t -g " + String.join(" ", remoteApkPaths));
-    }
-
-    private String pushApkToRemote(String apkName, String remoteDirPath)
-            throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
-        final File apk = buildHelper.getTestFile(apkName);
-        final String remoteApkPath = remoteDirPath + "/" + apk.getName();
-        assertTrue(getDevice().pushFile(apk, remoteApkPath));
-        assertNotNull(apk);
-        return remoteApkPath;
-    }
-
-    private long getTestFileSize(String fileName) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
-        final File file = buildHelper.getTestFile(fileName);
-        return file.length();
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
index 87f6840..3e2de0a 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
@@ -143,7 +143,6 @@
         // ADB disconnection causes failure of getUid(). Move up here before turnScreenOff().
         final int EXPECTED_UID = getUid();
 
-
         turnScreenOn(); // To ensure that the ScreenOff later gets logged.
         // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
         // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
@@ -190,7 +189,7 @@
         long statsdDurationMs = statsdWakelockData.get(EXPECTED_UID)
                 .get(EXPECTED_TAG_HASH) / 1_000_000;
         assertWithMessage(
-                "Wakelock in statsd with uid %s and tag %s was too short or too long", 
+                "Wakelock in statsd with uid %s and tag %s was too short or too long",
                 EXPECTED_UID, EXPECTED_TAG
         ).that(statsdDurationMs).isIn(Range.closed((long) MIN_DURATION, (long) MAX_DURATION));
 
diff --git a/hostsidetests/statsdatom/Android.bp b/hostsidetests/statsdatom/Android.bp
new file mode 100644
index 0000000..4d3051e
--- /dev/null
+++ b/hostsidetests/statsdatom/Android.bp
@@ -0,0 +1,58 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "CtsStatsdAtomHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: [
+        "src/**/statsd/UidAtomTestsTemp.java",
+    ],
+
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    libs: [
+        "compatibility-host-util",
+        "core_cts_test_resources",
+        "cts-tradefed",
+        "host-libprotobuf-java-full",
+        "platformprotos",
+        "tradefed",
+        "truth-prebuilt",
+    ],
+
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
+        "perfetto_config-full",
+    ],
+
+    data: [
+        ":CtsStatsdAtomApp",
+    ]
+}
+
+java_library_host {
+    name: "cts-statsd-atom-host-test-utils",
+    srcs: ["src/**/lib/*.java"],
+    libs: [
+        "compatibility-host-util",
+        "cts-tradefed",
+        "host-libprotobuf-java-full",
+        "platformprotos",
+        "tradefed",
+        "truth-prebuilt",
+    ],
+}
diff --git a/hostsidetests/statsdatom/AndroidTest.xml b/hostsidetests/statsdatom/AndroidTest.xml
new file mode 100644
index 0000000..e353d1a
--- /dev/null
+++ b/hostsidetests/statsdatom/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS statsd atom hostside tests">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="statsd" />
+    <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsStatsdAtomHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/statsdatom/OWNERS b/hostsidetests/statsdatom/OWNERS
new file mode 100644
index 0000000..062d0dd
--- /dev/null
+++ b/hostsidetests/statsdatom/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 366902
+jeffreyhuang@google.com
+jtnguyen@google.com
+muhammadq@google.com
+ruchirr@google.com
+singhtejinder@google.com
+tsaichristine@google.com
+yro@google.com
diff --git a/hostsidetests/statsdatom/apps/statsdapp/Android.bp b/hostsidetests/statsdatom/apps/statsdapp/Android.bp
new file mode 100644
index 0000000..79a69f7
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/Android.bp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+android_test_helper_app {
+    name: "CtsStatsdAtomApp",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    min_sdk_version: "28",
+    srcs: [
+        "src/**/*.java",
+        ":statslog-statsdatom-cts-java-gen",
+    ],
+    libs: [
+        "android.test.runner",
+        "junit",
+        "org.apache.http.legacy",
+    ],
+    privileged: true,
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+        "androidx.legacy_legacy-support-v4",
+        "androidx.test.rules",
+    ],
+    compile_multilib: "both",
+}
+
+genrule {
+    name: "statslog-statsdatom-cts-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module cts --javaPackage com.android.server.cts.device.statsdatom --javaClass StatsLogStatsdCts",
+    out: ["com/android/server/cts/device/statsdatom/StatsLogStatsdCts.java"],
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
new file mode 100644
index 0000000..227379b
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.server.cts.device.statsdatom"
+     android:versionCode="10">
+
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"/>
+    <uses-permission android:name="android.permission.DUMP"/> <!-- must be granted via pm grant -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.READ_SYNC_STATS"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
+
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
+
+        <service android:name=".IsolatedProcessService"
+             android:isolatedProcess="true"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.server.cts.device.statsdatom"
+         android:label="CTS tests of atoms used to collect stats from statsd">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/statsdatom/apps/statsdapp/res/values/strings.xml b/hostsidetests/statsdatom/apps/statsdapp/res/values/strings.xml
new file mode 100644
index 0000000..9dde420
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">CTS Statsd Atoms App</string>
+</resources>
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
new file mode 100644
index 0000000..0375650
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsdatom;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+public final class AtomTests {
+    private static final String TAG = AtomTests.class.getSimpleName();
+
+    @Test
+    // Start the isolated service, which logs an AppBreadcrumbReported atom, and then exit.
+    public void testIsolatedProcessService() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        Intent intent = new Intent(context, IsolatedProcessService.class);
+        context.startService(intent);
+        Thread.sleep(500);
+        context.stopService(intent);
+    }
+
+    @Test
+    // Make the app do some trivial work
+    public void testSimpleCpu() throws Exception {
+        long timestamp = System.currentTimeMillis();
+        for (int i = 0; i < 10000; i++) {
+            timestamp += 1;
+        }
+        Log.i(TAG, "The answer is " + timestamp);
+    }
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/IsolatedProcessService.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/IsolatedProcessService.java
new file mode 100644
index 0000000..abc3a49
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/IsolatedProcessService.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsdatom;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.StatsLog;
+
+public class IsolatedProcessService extends Service {
+    private static final String TAG = "IsolatedProcessService";
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        StatsLog.logStart(/*label=*/0);
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java
new file mode 100644
index 0000000..7b635d5
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.lib;
+
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+/**
+ * Contains miscellaneous helper functions that are used in statsd atom tests
+ */
+public final class AtomTestUtils {
+
+    public static final int WAIT_TIME_SHORT = 500;
+    public static final int WAIT_TIME_LONG = 1000;
+
+    /**
+     * Sends an AppBreadcrumbReported atom to statsd. For GaugeMetrics that are added using
+     * ConfigUtils, pulls are triggered when statsd receives an AppBreadcrumbReported atom, so
+     * calling this function is necessary for gauge data to be acquired.
+     *
+     * @param device test device can be retrieved using getDevice()
+     */
+    public static void sendAppBreadcrumbReportedAtom(ITestDevice device)
+            throws DeviceNotAvailableException {
+        String cmd = String.format("cmd stats log-app-breadcrumb %d %d", /*label=*/1,
+                AppBreadcrumbReported.State.START.ordinal());
+        device.executeShellCommand(cmd);
+    }
+
+    private AtomTestUtils() {}
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
new file mode 100644
index 0000000..83dca7f
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.lib;
+
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.EventMetric;
+import com.android.internal.os.StatsdConfigProto.FieldFilter;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.GaugeMetric;
+import com.android.internal.os.StatsdConfigProto.MessageMatcher;
+import com.android.internal.os.StatsdConfigProto.Position;
+import com.android.internal.os.StatsdConfigProto.Predicate;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.SimplePredicate;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.os.AtomsProto.Atom;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+public final class ConfigUtils {
+    public static final long CONFIG_ID = "cts_config".hashCode(); // evaluates to -1572883457
+    public static final String CONFIG_ID_STRING = String.valueOf(CONFIG_ID);
+
+    // Attribution chains are the first field in atoms.
+    private static final int ATTRIBUTION_CHAIN_FIELD_NUMBER = 1;
+    // Uids are the first field in attribution nodes.
+    private static final int ATTRIBUTION_NODE_UID_FIELD_NUMBER = 1;
+    // Uids as standalone fields are the first field in atoms.
+    private static final int UID_FIELD_NUMBER = 1;
+
+    // adb shell commands
+    private static final String UPDATE_CONFIG_CMD = "cmd stats config update";
+    private static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
+
+    /**
+     * Create a new config with common fields filled out, such as allowed log sources and
+     * default pull packages.
+     *
+     * @param pkgName test app package from which pushed atoms will be sent
+     */
+    public static StatsdConfig.Builder createConfigBuilder(String pkgName) {
+        return StatsdConfig.newBuilder()
+                .setId(CONFIG_ID)
+                .addAllowedLogSource("AID_SYSTEM")
+                .addAllowedLogSource("AID_BLUETOOTH")
+                // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
+                .addAllowedLogSource("com.android.bluetooth")
+                .addAllowedLogSource("AID_LMKD")
+                .addAllowedLogSource("AID_RADIO")
+                .addAllowedLogSource("AID_ROOT")
+                .addAllowedLogSource("AID_STATSD")
+                .addAllowedLogSource("com.android.systemui")
+                .addAllowedLogSource(pkgName)
+                .addDefaultPullPackages("AID_RADIO")
+                .addDefaultPullPackages("AID_SYSTEM")
+                .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
+    }
+
+    /**
+     * Adds an event metric for the specified atom. The atom should contain a uid either within
+     * an attribution chain or as a standalone field. Only those atoms which contain the uid of
+     * the test app will be included in statsd's report.
+     *
+     * @param config
+     * @param atomId index of atom within atoms.proto
+     * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false,
+     *    uid is a standalone field
+     * @param pkgName test app package from which atom will be logged
+     */
+    public static void addEventMetricForUidAtom(StatsdConfig.Builder config, int atomId,
+            boolean uidInAttributionChain, String pkgName) {
+        FieldValueMatcher.Builder fvm = createUidFvm(uidInAttributionChain, pkgName);
+        addEventMetric(config, atomId, Arrays.asList(fvm));
+    }
+
+    /**
+     * Adds an event metric for the specified atom. All such atoms received by statsd will be
+     * included in the report. If only atoms meeting certain constraints should be added to the
+     * report, use #addEventMetric(int atomId, List<FieldValueMatcher.Builder> fvms instead.
+     *
+     * @param config
+     * @param atomId index of atom within atoms.proto
+     */
+    public static void addEventMetric(StatsdConfig.Builder config, int atomId) {
+        addEventMetric(config, atomId, /*fvms=*/null);
+    }
+
+    /**
+     * Adds an event metric to the config for the specified atom. The atom's fields must meet
+     * the constraints specified in fvms for the atom to be included in statsd's report.
+     *
+     * @param config
+     * @param atomId index of atom within atoms.proto
+     * @param fvms list of constraints that atoms are filtered on
+     */
+    public static void addEventMetric(StatsdConfig.Builder config, int atomId,
+            @Nullable List<FieldValueMatcher.Builder> fvms) {
+        final String matcherName = "Atom matcher" + System.nanoTime();
+        final String eventName = "Event " + System.nanoTime();
+
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+        if (fvms != null) {
+            for (FieldValueMatcher.Builder fvm : fvms) {
+                sam.addFieldValueMatcher(fvm);
+            }
+        }
+
+        config.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(matcherName.hashCode())
+                .setSimpleAtomMatcher(sam));
+        config.addEventMetric(EventMetric.newBuilder()
+                .setId(eventName.hashCode())
+                .setWhat(matcherName.hashCode()));
+    }
+
+    /**
+     * Adds a gauge metric for a pulled atom with a uid field to the config. The atom will be
+     * pulled when an AppBreadcrumbReported atom is logged to statsd, and only those pulled atoms
+     * containing the uid of the test app will be included in statsd's report.
+     *
+     * @param config
+     * @param atomId index of atom within atoms.proto
+     * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false, uid
+     *    is a standalone field
+     * @param pkgName test app package from which atom will be logged
+     */
+    public static void addGaugeMetricForUidAtom(StatsdConfig.Builder config, int atomId,
+            boolean uidInAttributionChain, String pkgName) {
+        addGaugeMetricInternal(config, atomId, /*filterByUid=*/true, uidInAttributionChain, pkgName,
+                /*dimensionsInWhat=*/null);
+    }
+
+    /**
+     * Equivalent to addGaugeMetricForUidAtom except that the output in the report is sliced by the
+     * specified dimensions.
+     *
+     * @param dimensionsInWhat dimensions to slice the output by
+     */
+    public static void addGaugeMetricForUidAtomWithDimensions(StatsdConfig.Builder config,
+            int atomId, boolean uidInAttributionChain, String pkgName,
+            FieldMatcher.Builder dimensionsInWhat) {
+        addGaugeMetricInternal(config, atomId, /*filterByUid=*/true, uidInAttributionChain, pkgName,
+                dimensionsInWhat);
+    }
+
+    /**
+     * Adds a gauge metric for a pulled atom to the config. The atom will be pulled when an
+     * AppBreadcrumbReported atom is logged to statsd.
+     *
+     * @param config
+     * @param atomId index of the atom within atoms.proto
+     * @param dimensionsInWhat dimensions to slice the output by
+     */
+    public static void addGaugeMetric(StatsdConfig.Builder config, int atomId) {
+        addGaugeMetricInternal(config, atomId, /*filterByUid=*/false,
+                /*uidInAttributionChain=*/false, /*pkgName=*/null, /*dimensionsInWhat=*/null);
+    }
+
+    /**
+     * Equivalent to addGaugeMetric except that output in the report is sliced by the specified
+     * dimensions.
+     *
+     * @param dimensionsInWhat dimensions to slice the output by
+     */
+    public static void addGaugeMetricWithDimensions(StatsdConfig.Builder config, int atomId,
+            FieldMatcher.Builder dimensionsInWhat) {
+        addGaugeMetricInternal(config, atomId, /*filterByUid=*/false,
+                /*uidInAttributionChain=*/false, /*pkgName=*/null, dimensionsInWhat);
+    }
+
+    private static void addGaugeMetricInternal(StatsdConfig.Builder config, int atomId,
+            boolean filterByUid, boolean uidInAttributionChain, @Nullable String pkgName,
+            @Nullable FieldMatcher.Builder dimensionsInWhat) {
+        final String gaugeName = "Gauge metric " + System.nanoTime();
+        final String whatName = "What atom matcher " + System.nanoTime();
+        final String triggerName = "Trigger atom matcher " + System.nanoTime();
+
+        // Add atom matcher for "what"
+        SimpleAtomMatcher.Builder whatMatcher = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+        if (filterByUid && pkgName != null) {
+            whatMatcher.addFieldValueMatcher(createUidFvm(uidInAttributionChain, pkgName));
+        }
+        config.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(whatName.hashCode())
+                .setSimpleAtomMatcher(whatMatcher));
+
+        // Add atom matcher for trigger event
+        SimpleAtomMatcher.Builder triggerMatcher = SimpleAtomMatcher.newBuilder()
+                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
+        config.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(triggerName.hashCode())
+                .setSimpleAtomMatcher(triggerMatcher));
+
+        // Add gauge metric
+        GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
+                .setId(gaugeName.hashCode())
+                .setWhat(whatName.hashCode())
+                .setTriggerEvent(triggerName.hashCode())
+                .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
+                .setBucket(TimeUnit.CTS)
+                .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES)
+                .setMaxNumGaugeAtomsPerBucket(10_000);
+        if (dimensionsInWhat != null) {
+            gaugeMetric.setDimensionsInWhat(dimensionsInWhat.build());
+        }
+        config.addGaugeMetric(gaugeMetric.build());
+    }
+
+    /**
+     * Creates a FieldValueMatcher.Builder object that matches atoms whose uid field is equal to
+     * the uid of pkgName.
+     *
+     * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false, uid
+     * is a standalone field
+     * @param pkgName test app package from which atom will be logged
+     */
+    private static FieldValueMatcher.Builder createUidFvm(boolean uidInAttributionChain,
+            String pkgName) {
+        if (uidInAttributionChain) {
+            FieldValueMatcher.Builder nodeFvm = createFvm(ATTRIBUTION_NODE_UID_FIELD_NUMBER)
+                    .setEqString(pkgName);
+            return createFvm(ATTRIBUTION_CHAIN_FIELD_NUMBER)
+                    .setPosition(Position.ANY)
+                    .setMatchesTuple(MessageMatcher.newBuilder().addFieldValueMatcher(nodeFvm));
+        } else {
+            return createFvm(UID_FIELD_NUMBER).setEqString(pkgName);
+        }
+    }
+
+    /**
+     * Creates a FieldValueMatcher.Builder for a particular field. Note that the value still needs
+     * to be set.
+     *
+     * @param fieldNumber index of field within the atom
+     */
+    private static FieldValueMatcher.Builder createFvm(int fieldNumber) {
+        return FieldValueMatcher.newBuilder().setField(fieldNumber);
+    }
+
+    /**
+     * Upload a config to statsd.
+     */
+    public static void uploadConfig(ITestDevice device, StatsdConfig.Builder configBuilder)
+            throws Exception {
+        StatsdConfig config = configBuilder.build();
+        CLog.d("Uploading the following config to statsd:\n" + config.toString());
+
+        File configFile = File.createTempFile("statsdconfig", ".config");
+        configFile.deleteOnExit();
+        Files.write(config.toByteArray(), configFile);
+
+        // Push config to temporary location
+        String remotePath = "/data/local/tmp/" + configFile.getName();
+        device.pushFile(configFile, remotePath);
+
+        // Send config to statsd
+        device.executeShellCommand(String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
+                CONFIG_ID_STRING));
+
+        // Remove config from temporary location
+        device.executeShellCommand("rm " + remotePath);
+    }
+
+    /**
+     * Removes any pre-existing CTS configs from statsd.
+     */
+    public static void removeConfig(ITestDevice device) throws Exception {
+        device.executeShellCommand(String.join(" ", REMOVE_CONFIG_CMD, CONFIG_ID_STRING));
+    }
+
+    private ConfigUtils() {}
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
new file mode 100644
index 0000000..bb6a393
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.lib;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+import java.io.FileNotFoundException;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Contains utility functions for interacting with the device.
+ * Largely copied from incident's ProtoDumpTestCase.
+ */
+public final class DeviceUtils {
+    public static final String STATSD_ATOM_TEST_APK = "CtsStatsdAtomApp.apk";
+    public static final String STATSD_ATOM_TEST_PKG = "com.android.server.cts.device.statsdatom";
+
+    private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+    // feature names
+    public static final String FEATURE_WATCH = "android.hardware.type.watch";
+
+    /**
+     * Runs device side tests.
+     *
+     * @param device Can be retrieved by running getDevice() in a class that extends DeviceTestCase
+     * @param pkgName Test package name, such as "com.android.server.cts.statsdatom"
+     * @param testClassName Test class name which can either be a fully qualified name or "." + a
+     *     class name; if null, all test in the package will be run
+     * @param testMethodName Test method name; if null, all tests in class or package will be run
+     * @return {@link TestRunResult} of this invocation
+     * @throws DeviceNotAvailableException
+     */
+    public static @Nonnull TestRunResult runDeviceTests(ITestDevice device, String pkgName,
+            @Nullable String testClassName, @Nullable String testMethodName)
+            throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+                pkgName, TEST_RUNNER, device.getIDevice());
+        if (testClassName != null && testMethodName != null) {
+            testRunner.setMethodName(testClassName, testMethodName);
+        } else if (testClassName != null) {
+            testRunner.setClassName(testClassName);
+        }
+
+        CollectingTestListener listener = new CollectingTestListener();
+        assertThat(device.runInstrumentationTests(testRunner, listener)).isTrue();
+
+        final TestRunResult result = listener.getCurrentRunResults();
+        if (result.isRunFailure()) {
+            throw new Error("Failed to successfully run device tests for "
+                    + result.getName() + ": " + result.getRunFailureMessage());
+        }
+        if (result.getNumTests() == 0) {
+            throw new Error("No tests were run on the device");
+        }
+        if (result.hasFailedTests()) {
+            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
+                    result.getTestResults().entrySet()) {
+                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+                    errorBuilder.append(resultEntry.getKey().toString());
+                    errorBuilder.append(":\n");
+                    errorBuilder.append(resultEntry.getValue().getStackTrace());
+                }
+            }
+            throw new AssertionError(errorBuilder.toString());
+        }
+        return result;
+    }
+
+    /**
+     * Runs device side tests from the com.android.server.cts.device.statsdatom package.
+     */
+    public static @Nonnull TestRunResult runDeviceTestsOnStatsdApp(ITestDevice device,
+            @Nullable String testClassName, @Nullable String testMethodName)
+            throws DeviceNotAvailableException {
+        return runDeviceTests(device, STATSD_ATOM_TEST_PKG, testClassName, testMethodName);
+    }
+
+    /**
+     * Install the statsdatom CTS app to the device.
+     */
+    public static void installStatsdTestApp(ITestDevice device, IBuildInfo ctsBuildInfo)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        installTestApp(device, STATSD_ATOM_TEST_APK, STATSD_ATOM_TEST_PKG, ctsBuildInfo);
+    }
+
+    /**
+     * Install a test app to the device.
+     */
+    public static void installTestApp(ITestDevice device, String apkName, String pkgName,
+            IBuildInfo ctsBuildInfo) throws FileNotFoundException, DeviceNotAvailableException {
+        CLog.d("Installing app " + apkName);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(ctsBuildInfo);
+        final String result = device.installPackage(
+                buildHelper.getTestFile(apkName), /*reinstall=*/true, /*grantPermissions=*/true);
+        assertWithMessage("Failed to install " + apkName + ": " + result).that(result).isNull();
+        allowBackgroundServices(device, pkgName);
+    }
+
+    /**
+     * Required to successfully start a background service from adb, starting in O.
+     */
+    private static void allowBackgroundServices(ITestDevice device, String pkgName)
+            throws DeviceNotAvailableException {
+        String cmd = "cmd deviceidle tempwhitelist " + pkgName;
+        device.executeShellCommand(cmd);
+    }
+
+    /**
+     * Uninstall the statsdatom CTS app from the device.
+     */
+    public static void uninstallStatsdTestApp(ITestDevice device) throws Exception {
+        uninstallTestApp(device, STATSD_ATOM_TEST_PKG);
+    }
+
+    /**
+     * Uninstall the test app from the device.
+     */
+    public static void uninstallTestApp(ITestDevice device, String pkgName) throws Exception {
+        device.uninstallPackage(pkgName);
+    }
+
+    /**
+     * Run an adb shell command on device and parse the results as a proto of a given type.
+     *
+     * @param device Device to run cmd on
+     * @param parser Protobuf parser object, which can be retrieved by running MyProto.parser()
+     * @param cmd The adb shell command to run (e.g. "cmd stats update config")
+     *
+     * @throws DeviceNotAvailableException
+     * @throws InvalidProtocolBufferException Occurs if there was an error parsing the proto. Note
+     *     that a 0 length buffer is not necessarily an error.
+     * @return Proto of specified type
+     */
+    public static <T extends MessageLite> T getShellCommandOutput(@Nonnull ITestDevice device,
+            Parser<T> parser, String cmd)
+            throws DeviceNotAvailableException, InvalidProtocolBufferException {
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        device.executeShellCommand(cmd, receiver);
+        try {
+            return parser.parseFrom(receiver.getOutput());
+        } catch (Exception ex) {
+            CLog.d("Error parsing " + parser.getClass().getCanonicalName() + " for cmd " + cmd);
+            throw ex;
+        }
+    }
+
+    /**
+     * Returns the UID of the host, which should always either be AID_SHELL (2000) or AID_ROOT (0).
+     */
+    public static int getHostUid(ITestDevice device) throws DeviceNotAvailableException {
+        String uidString = "";
+        try {
+            uidString = device.executeShellCommand("id -u");
+            return Integer.parseInt(uidString.trim());
+        } catch (NumberFormatException ex) {
+            CLog.e("Failed to get host's uid via shell command. Found " + uidString);
+            // Fall back to alternative method...
+            if (device.isAdbRoot()) {
+                return 0;
+            } else {
+                return 2000; // SHELL
+            }
+        }
+    }
+
+    /**
+     * Returns the UID of the statsdatom CTS test app.
+     */
+    public static int getStatsdTestAppUid(ITestDevice device) throws DeviceNotAvailableException {
+        return getAppUid(device, STATSD_ATOM_TEST_PKG);
+    }
+
+    /**
+     * Returns the UID of the test app.
+     */
+    public static int getAppUid(ITestDevice device, String pkgName)
+            throws DeviceNotAvailableException {
+        int currentUser = device.getCurrentUser();
+        String uidLine = device.executeShellCommand("cmd package list packages -U --user "
+                + currentUser + " " + pkgName);
+        String[] uidLineArr = uidLine.split(":");
+
+        // Package uid is located at index 2.
+        assertThat(uidLineArr.length).isGreaterThan(2);
+        int appUid = Integer.parseInt(uidLineArr[2].trim());
+        assertThat(appUid).isGreaterThan(10000);
+        return appUid;
+    }
+
+    /**
+     * Determines if the device has the given features.
+     *
+     * @param feature name of the feature (e.g. "android.hardware.bluetooth")
+     */
+    public static boolean hasFeature(ITestDevice device, String feature) throws Exception {
+        final String features = device.executeShellCommand("pm list features");
+        return features.contains(feature);
+    }
+
+    private DeviceUtils() {}
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ReportUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ReportUtils.java
new file mode 100644
index 0000000..97914cd
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ReportUtils.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.lib;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.os.StatsLog.GaugeBucketInfo;
+import com.android.os.StatsLog.GaugeMetricData;
+import com.android.os.StatsLog.StatsLogReport;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+public final class ReportUtils {
+    private static final String DUMP_REPORT_CMD = "cmd stats dump-report";
+    private static final long NS_PER_SEC = (long) 1E+9;
+
+    /**
+     * Returns a list of event metrics, which is sorted by timestamp, from the statsd report.
+     * Note: Calling this function deletes the report from statsd.
+     */
+    public static List<EventMetricData> getEventMetricDataList(ITestDevice device)
+            throws Exception {
+        ConfigMetricsReportList reportList = getReportList(device);
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<EventMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getEventMetrics().getDataList());
+        }
+        data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
+
+        CLog.d("Got the following EventMetricDataList:\n");
+        for (EventMetricData d : data) {
+            CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
+        }
+        return data;
+    }
+
+    public static List<Atom> getGaugeMetricAtoms(ITestDevice device) throws Exception {
+        return getGaugeMetricAtoms(device, /*checkTimestampTruncated=*/false);
+    }
+
+    /**
+     * Returns a list of gauge atoms from the statsd report. Assumes that there is only one bucket
+     * for the gauge metric.
+     * Note: calling this function deletes the report from statsd.
+     *
+     * @param checkTimestampTrucated if true, checks that atom timestmaps are properly truncated
+     */
+    public static List<Atom> getGaugeMetricAtoms(ITestDevice device,
+            boolean checkTimestampTruncated) throws Exception {
+        ConfigMetricsReportList reportList = getReportList(device);
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+        assertThat(report.getMetricsCount()).isEqualTo(1);
+
+        List<Atom> atoms = new ArrayList<>();
+        for (GaugeMetricData d : report.getMetrics(0).getGaugeMetrics().getDataList()) {
+            assertThat(d.getBucketInfoCount()).isEqualTo(1);
+            GaugeBucketInfo bucketInfo = d.getBucketInfo(0);
+            atoms.addAll(bucketInfo.getAtomList());
+            if (checkTimestampTruncated) {
+                for (long timestampNs: bucketInfo.getElapsedTimestampNanosList()) {
+                    assertTimestampIsTruncated(timestampNs);
+                }
+            }
+        }
+
+        CLog.d("Got the following GaugeMetric atoms:\n");
+        for (Atom atom : atoms) {
+            CLog.d("Atom:\n" + atom.toString());
+        }
+        return atoms;
+    }
+
+    /**
+     * Delete all pre-existing reports corresponding to the CTS config.
+     */
+    public static void clearReports(ITestDevice device) throws Exception {
+        getReportList(device);
+    }
+
+    /**
+     * Retrieves the ConfigMetricsReports corresponding to the CTS config from statsd.
+     * Note: Calling this functions deletes the report from statsd.
+     */
+    private static ConfigMetricsReportList getReportList(ITestDevice device) throws Exception {
+        try {
+            String cmd = String.join(" ", DUMP_REPORT_CMD, ConfigUtils.CONFIG_ID_STRING,
+                    "--include_current_bucket", "--proto");
+            ConfigMetricsReportList reportList = DeviceUtils.getShellCommandOutput(device,
+                    ConfigMetricsReportList.parser(), cmd);
+            return reportList;
+        } catch (InvalidProtocolBufferException ex) {
+            int hostUid = DeviceUtils.getHostUid(device);
+            CLog.e("Failed to fetch and parse the statsd output report. Perhaps there is not a "
+                    + "valid statsd config for the requested uid=" + hostUid + ", id="
+                    + ConfigUtils.CONFIG_ID + ".");
+            throw ex;
+        }
+    }
+
+    /**
+     * Checks that a timestamp has been truncated to a multiple of 5 min.
+     */
+    private static void assertTimestampIsTruncated(long timestampNs) {
+        long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
+        assertWithMessage("Timestamp is not truncated")
+                .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
+    }
+
+    private ReportUtils() {}
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/TestLibrary.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/TestLibrary.java
new file mode 100644
index 0000000..f80d758
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/TestLibrary.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.lib;
+
+public final class TestLibrary {
+
+    /**
+     * Calculates the nth fibonacci number
+     */
+    public static int fib(int n) {
+        if (n <= 2) {
+            return 1;
+        }
+
+        int a = 1;
+        int b = 1;
+        for (int i = 3; i <= n; i++) {
+            int c = a + b;
+            a = b;
+            b = c;
+        }
+        return b;
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/AtomTestCase.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/AtomTestCase.java
new file mode 100644
index 0000000..8c820d2
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/AtomTestCase.java
@@ -0,0 +1,1157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsdatom.statsd;
+
+import static android.cts.statsdatom.statsd.DeviceAtomTestCase.DEVICE_SIDE_TEST_APK;
+import static android.cts.statsdatom.statsd.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.BatteryStatsProto;
+import android.os.StatsDataDumpProto;
+import android.service.battery.BatteryServiceDumpProto;
+import android.service.batterystats.BatteryStatsServiceDumpProto;
+import android.service.procstats.ProcessStatsServiceDumpProto;
+
+import com.android.annotations.Nullable;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.EventMetric;
+import com.android.internal.os.StatsdConfigProto.FieldFilter;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.GaugeMetric;
+import com.android.internal.os.StatsdConfigProto.Predicate;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.SimplePredicate;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.ProcessStatsPackageProto;
+import com.android.os.AtomsProto.ProcessStatsProto;
+import com.android.os.AtomsProto.ProcessStatsStateProto;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.DurationMetricData;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.os.StatsLog.GaugeBucketInfo;
+import com.android.os.StatsLog.GaugeMetricData;
+import com.android.os.StatsLog.CountMetricData;
+import com.android.os.StatsLog.StatsLogReport;
+import com.android.os.StatsLog.ValueMetricData;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import com.google.common.collect.Range;
+import com.google.common.io.Files;
+import com.google.protobuf.ByteString;
+
+import perfetto.protos.PerfettoConfig.DataSourceConfig;
+import perfetto.protos.PerfettoConfig.FtraceConfig;
+import perfetto.protos.PerfettoConfig.TraceConfig;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Random;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * Base class for testing Statsd atoms.
+ * Validates reporting of statsd logging based on different events
+ */
+public class AtomTestCase extends BaseTestCase {
+
+    /**
+     * Run tests that are optional; they are not valid CTS tests per se, since not all devices can
+     * be expected to pass them, but can be run, if desired, to ensure they work when appropriate.
+     */
+    public static final boolean OPTIONAL_TESTS_ENABLED = false;
+
+    public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
+    public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
+    public static final String DUMP_BATTERY_CMD = "dumpsys battery";
+    public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats";
+    public static final String DUMPSYS_STATS_CMD = "dumpsys stats";
+    public static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
+    public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
+    /** ID of the config, which evaluates to -1572883457. */
+    public static final long CONFIG_ID = "cts_config".hashCode();
+
+    public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
+    public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+    public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
+    public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
+    public static final String FEATURE_CAMERA = "android.hardware.camera";
+    public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
+    public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
+    public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
+    public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
+    public static final String FEATURE_PC = "android.hardware.type.pc";
+    public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
+    public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+    public static final String FEATURE_WATCH = "android.hardware.type.watch";
+    public static final String FEATURE_WIFI = "android.hardware.wifi";
+    public static final String FEATURE_INCREMENTAL_DELIVERY =
+            "android.software.incremental_delivery";
+
+    // Telephony phone types
+    public static final int PHONE_TYPE_GSM = 1;
+    public static final int PHONE_TYPE_CDMA = 2;
+    public static final int PHONE_TYPE_CDMA_LTE = 6;
+
+    protected static final int WAIT_TIME_SHORT = 500;
+    protected static final int WAIT_TIME_LONG = 2_000;
+
+    protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
+    protected static final long SCREEN_STATE_POLLING_INTERVAL = 500;
+
+    protected static final long NS_PER_SEC = (long) 1E+9;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // Uninstall to clear the history in case it's still on the device.
+        removeConfig(CONFIG_ID);
+        getReportList(); // Clears data.
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        removeConfig(CONFIG_ID);
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    /**
+     * Determines whether logcat indicates that incidentd fired since the given device date.
+     */
+    protected boolean didIncidentdFireSince(String date) throws Exception {
+        final String INCIDENTD_TAG = "incidentd";
+        final String INCIDENTD_STARTED_STRING = "reportIncident";
+        // TODO: Do something more robust than this in case of delayed logging.
+        Thread.sleep(1000);
+        String log = getLogcatSince(date, String.format(
+                "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
+        return log.contains(INCIDENTD_STARTED_STRING);
+    }
+
+    protected boolean checkDeviceFor(String methodName) throws Exception {
+        try {
+            installPackage(DEVICE_SIDE_TEST_APK, true);
+            runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".Checkers", methodName);
+            // Test passes, meaning that the answer is true.
+            LogUtil.CLog.d(methodName + "() indicates true.");
+            return true;
+        } catch (AssertionError e) {
+            // Method is designed to fail if the answer is false.
+            LogUtil.CLog.d(methodName + "() indicates false.");
+            return false;
+        }
+    }
+
+    /**
+     * Returns a protobuf-encoded perfetto config that enables the kernel
+     * ftrace tracer with sched_switch for 10 seconds.
+     */
+    protected ByteString getPerfettoConfig() {
+        TraceConfig.Builder builder = TraceConfig.newBuilder();
+
+        TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig
+            .newBuilder()
+            .setSizeKb(128)
+            .build();
+        builder.addBuffers(buffer);
+
+        FtraceConfig ftraceConfig = FtraceConfig.newBuilder()
+            .addFtraceEvents("sched/sched_switch")
+            .build();
+        DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
+            .setName("linux.ftrace")
+            .setTargetBuffer(0)
+            .setFtraceConfig(ftraceConfig)
+            .build();
+        TraceConfig.DataSource dataSource = TraceConfig.DataSource
+            .newBuilder()
+            .setConfig(dataSourceConfig)
+            .build();
+        builder.addDataSources(dataSource);
+
+        builder.setDurationMs(10000);
+        builder.setAllowUserBuildTracing(true);
+
+        // To avoid being hit with guardrails firing in multiple test runs back
+        // to back, we set a unique session key for each config.
+        Random random = new Random();
+        StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-");
+        sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
+        builder.setUniqueSessionName(sessionNameBuilder.toString());
+
+        return builder.build().toByteString();
+    }
+
+    /**
+     * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's
+     * run too close of for too many times and hits the upload limit.
+     */
+    protected void resetPerfettoGuardrails() throws Exception {
+        final String cmd = "perfetto --reset-guardrails";
+        CommandResult cr = getDevice().executeShellV2Command(cmd);
+        if (cr.getStatus() != CommandStatus.SUCCESS)
+            throw new Exception(String.format("Error while executing %s: %s %s", cmd, cr.getStdout(), cr.getStderr()));
+    }
+
+    private String probe(String path) throws Exception {
+        return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
+                + " cat " + path + " ; else echo -1 ; fi");
+    }
+
+    /**
+     * Determines whether perfetto enabled the kernel ftrace tracer.
+     */
+    protected boolean isSystemTracingEnabled() throws Exception {
+        final String traceFsPath = "/sys/kernel/tracing/tracing_on";
+        String tracing_on = probe(traceFsPath);
+        if (tracing_on.startsWith("0")) return false;
+        if (tracing_on.startsWith("1")) return true;
+
+        // fallback to debugfs
+        LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
+                tracing_on);
+
+        final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
+        tracing_on = probe(debugFsPath);
+        if (tracing_on.startsWith("0")) return false;
+        if (tracing_on.startsWith("1")) return true;
+        throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
+    }
+
+    protected static StatsdConfig.Builder createConfigBuilder() {
+      return StatsdConfig.newBuilder()
+          .setId(CONFIG_ID)
+          .addAllowedLogSource("AID_SYSTEM")
+          .addAllowedLogSource("AID_BLUETOOTH")
+          // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
+          .addAllowedLogSource("com.android.bluetooth")
+          .addAllowedLogSource("AID_LMKD")
+          .addAllowedLogSource("AID_RADIO")
+          .addAllowedLogSource("AID_ROOT")
+          .addAllowedLogSource("AID_STATSD")
+          .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE)
+          .addDefaultPullPackages("AID_RADIO")
+          .addDefaultPullPackages("AID_SYSTEM")
+          .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
+    }
+
+    protected void createAndUploadConfig(int atomTag) throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atomTag);
+        uploadConfig(conf);
+    }
+
+    protected void uploadConfig(StatsdConfig.Builder config) throws Exception {
+        uploadConfig(config.build());
+    }
+
+    protected void uploadConfig(StatsdConfig config) throws Exception {
+        LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
+        File configFile = File.createTempFile("statsdconfig", ".config");
+        configFile.deleteOnExit();
+        Files.write(config.toByteArray(), configFile);
+        String remotePath = "/data/local/tmp/" + configFile.getName();
+        getDevice().pushFile(configFile, remotePath);
+        getDevice().executeShellCommand(
+                String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
+                        String.valueOf(CONFIG_ID)));
+        getDevice().executeShellCommand("rm " + remotePath);
+    }
+
+    protected void removeConfig(long configId) throws Exception {
+        getDevice().executeShellCommand(
+                String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(configId)));
+    }
+
+    /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */
+    protected List<EventMetricData> getEventMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        return getEventMetricDataList(reportList);
+    }
+
+    /**
+     *  Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList.
+     */
+    protected List<ConfigMetricsReport> getSortedConfigMetricsReports(
+            ConfigMetricsReportList configMetricsReportList) {
+        return configMetricsReportList.getReportsList().stream()
+                .sorted(Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
+     * contain a single report).
+     */
+    protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
+            throws Exception {
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<EventMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getEventMetrics().getDataList());
+        }
+        data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
+
+        LogUtil.CLog.d("Get EventMetricDataList as following:\n");
+        for (EventMetricData d : data) {
+            LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
+        }
+        return data;
+    }
+
+    protected List<Atom> getGaugeMetricDataList() throws Exception {
+        return getGaugeMetricDataList(/*checkTimestampTruncated=*/false);
+    }
+
+    protected List<Atom> getGaugeMetricDataList(boolean checkTimestampTruncated) throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+
+        // only config
+        ConfigMetricsReport report = reportList.getReports(0);
+        assertThat(report.getMetricsCount()).isEqualTo(1);
+
+        List<Atom> data = new ArrayList<>();
+        for (GaugeMetricData gaugeMetricData :
+                report.getMetrics(0).getGaugeMetrics().getDataList()) {
+            assertThat(gaugeMetricData.getBucketInfoCount()).isEqualTo(1);
+            GaugeBucketInfo bucketInfo = gaugeMetricData.getBucketInfo(0);
+            for (Atom atom : bucketInfo.getAtomList()) {
+                data.add(atom);
+            }
+            if (checkTimestampTruncated) {
+                for (long timestampNs: bucketInfo.getElapsedTimestampNanosList()) {
+                    assertTimestampIsTruncated(timestampNs);
+                }
+            }
+        }
+
+        LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
+        for (Atom d : data) {
+            LogUtil.CLog.d("Atom:\n" + d.toString());
+        }
+        return data;
+    }
+
+    /**
+     * Gets the statsd report and extract duration metric data.
+     * Note that this also deletes that report from statsd.
+     */
+    protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<DurationMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getDurationMetrics().getDataList());
+        }
+
+        LogUtil.CLog.d("Got DurationMetricDataList as following:\n");
+        for (DurationMetricData d : data) {
+            LogUtil.CLog.d("Duration " + d);
+        }
+        return data;
+    }
+
+    /**
+     * Gets the statsd report and extract count metric data.
+     * Note that this also deletes that report from statsd.
+     */
+    protected List<CountMetricData> getCountMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<CountMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getCountMetrics().getDataList());
+        }
+
+        LogUtil.CLog.d("Got CountMetricDataList as following:\n");
+        for (CountMetricData d : data) {
+            LogUtil.CLog.d("Count " + d);
+        }
+        return data;
+    }
+
+    /**
+     * Gets the statsd report and extract value metric data.
+     * Note that this also deletes that report from statsd.
+     */
+    protected List<ValueMetricData> getValueMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<ValueMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getValueMetrics().getDataList());
+        }
+
+        LogUtil.CLog.d("Got ValueMetricDataList as following:\n");
+        for (ValueMetricData d : data) {
+            LogUtil.CLog.d("Value " + d);
+        }
+        return data;
+    }
+
+    protected StatsLogReport getStatsLogReport() throws Exception {
+        ConfigMetricsReport report = getConfigMetricsReport();
+        assertThat(report.hasUidMap()).isTrue();
+        assertThat(report.getMetricsCount()).isEqualTo(1);
+        return report.getMetrics(0);
+    }
+
+    protected ConfigMetricsReport getConfigMetricsReport() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        return reportList.getReports(0);
+    }
+
+    /** Gets the statsd report. Note that this also deletes that report from statsd. */
+    protected ConfigMetricsReportList getReportList() throws Exception {
+        try {
+            ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
+                    String.join(" ", DUMP_REPORT_CMD, String.valueOf(CONFIG_ID),
+                            "--include_current_bucket", "--proto"));
+            return reportList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to fetch and parse the statsd output report. "
+                    + "Perhaps there is not a valid statsd config for the requested "
+                    + "uid=" + getHostUid() + ", id=" + CONFIG_ID + ".");
+            throw (e);
+        }
+    }
+
+    protected BatteryStatsProto getBatteryStatsProto() throws Exception {
+        try {
+            BatteryStatsProto batteryStatsProto = getDump(BatteryStatsServiceDumpProto.parser(),
+                    String.join(" ", DUMP_BATTERYSTATS_CMD,
+                            "--proto")).getBatterystats();
+            LogUtil.CLog.d("Got batterystats:\n " + batteryStatsProto.toString());
+            return batteryStatsProto;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump batterystats proto");
+            throw (e);
+        }
+    }
+
+    /** Gets reports from the statsd data incident section from the stats dumpsys. */
+    protected List<ConfigMetricsReportList> getReportsFromStatsDataDumpProto() throws Exception {
+        try {
+            StatsDataDumpProto statsProto = getDump(StatsDataDumpProto.parser(),
+                    String.join(" ", DUMPSYS_STATS_CMD, "--proto"));
+            // statsProto holds repeated bytes, which we must parse into ConfigMetricsReportLists.
+            List<ConfigMetricsReportList> reports
+                    = new ArrayList<>(statsProto.getConfigMetricsReportListCount());
+            for (ByteString reportListBytes : statsProto.getConfigMetricsReportListList()) {
+                reports.add(ConfigMetricsReportList.parseFrom(reportListBytes));
+            }
+            LogUtil.CLog.d("Got dumpsys stats output:\n " + reports.toString());
+            return reports;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dumpsys stats proto");
+            throw (e);
+        }
+    }
+
+    protected List<ProcessStatsProto> getProcStatsProto() throws Exception {
+        try {
+
+            List<ProcessStatsProto> processStatsProtoList =
+                new ArrayList<ProcessStatsProto>();
+            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
+                    ProcessStatsServiceDumpProto.parser(),
+                    String.join(" ", DUMP_PROCSTATS_CMD,
+                            "--proto")).getProcstatsNow();
+            for (android.service.procstats.ProcessStatsProto stats :
+                    sectionProto.getProcessStatsList()) {
+                ProcessStatsProto procStats = ProcessStatsProto.parser().parseFrom(
+                    stats.toByteArray());
+                processStatsProtoList.add(procStats);
+            }
+            LogUtil.CLog.d("Got procstats:\n ");
+            for (ProcessStatsProto processStatsProto : processStatsProtoList) {
+                LogUtil.CLog.d(processStatsProto.toString());
+            }
+            return processStatsProtoList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump procstats proto");
+            throw (e);
+        }
+    }
+
+    /*
+     * Get all procstats package data in proto
+     */
+    protected List<ProcessStatsPackageProto> getAllProcStatsProto() throws Exception {
+        try {
+            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
+                    ProcessStatsServiceDumpProto.parser(),
+                    String.join(" ", DUMP_PROCSTATS_CMD,
+                            "--proto")).getProcstatsOver24Hrs();
+            List<ProcessStatsPackageProto> processStatsProtoList =
+                new ArrayList<ProcessStatsPackageProto>();
+            for (android.service.procstats.ProcessStatsPackageProto pkgStast :
+                sectionProto.getPackageStatsList()) {
+              ProcessStatsPackageProto pkgAtom =
+                  ProcessStatsPackageProto.parser().parseFrom(pkgStast.toByteArray());
+                processStatsProtoList.add(pkgAtom);
+            }
+            LogUtil.CLog.d("Got procstats:\n ");
+            for (ProcessStatsPackageProto processStatsProto : processStatsProtoList) {
+                LogUtil.CLog.d(processStatsProto.toString());
+            }
+            return processStatsProtoList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump procstats proto");
+            throw (e);
+        }
+    }
+
+    /*
+     * Get all processes' procstats statsd data in proto
+     */
+    protected List<android.service.procstats.ProcessStatsProto> getAllProcStatsProtoForStatsd()
+            throws Exception {
+        try {
+            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
+                    android.service.procstats.ProcessStatsSectionProto.parser(),
+                    String.join(" ", DUMP_PROCSTATS_CMD,
+                            "--statsd"));
+            List<android.service.procstats.ProcessStatsProto> processStatsProtoList
+                    = sectionProto.getProcessStatsList();
+            LogUtil.CLog.d("Got procstats:\n ");
+            for (android.service.procstats.ProcessStatsProto processStatsProto
+                    : processStatsProtoList) {
+                LogUtil.CLog.d(processStatsProto.toString());
+            }
+            return processStatsProtoList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump procstats proto");
+            throw (e);
+        }
+    }
+
+    protected boolean hasBattery() throws Exception {
+        try {
+            BatteryServiceDumpProto batteryProto = getDump(BatteryServiceDumpProto.parser(),
+                    String.join(" ", DUMP_BATTERY_CMD, "--proto"));
+            LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
+            return batteryProto.getIsPresent();
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump batteryservice proto");
+            throw (e);
+        }
+    }
+
+    /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
+    protected static FieldValueMatcher.Builder createFvm(int field) {
+        return FieldValueMatcher.newBuilder().setField(field);
+    }
+
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
+        addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the given key.
+     *
+     * @param conf    configuration
+     * @param atomTag atom tag (from atoms.proto)
+     * @param fvm     FieldValueMatcher.Builder for the relevant key
+     */
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
+            FieldValueMatcher.Builder fvm)
+            throws Exception {
+        addAtomEvent(conf, atomTag, Arrays.asList(fvm));
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the given keys.
+     *
+     * @param conf   configuration
+     * @param atomId atom tag (from atoms.proto)
+     * @param fvms   list of FieldValueMatcher.Builders to attach to the atom. May be null.
+     */
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomId,
+            List<FieldValueMatcher.Builder> fvms) throws Exception {
+
+        final String atomName = "Atom" + System.nanoTime();
+        final String eventName = "Event" + System.nanoTime();
+
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+        if (fvms != null) {
+            for (FieldValueMatcher.Builder fvm : fvms) {
+                sam.addFieldValueMatcher(fvm);
+            }
+        }
+        conf.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(atomName.hashCode())
+                .setSimpleAtomMatcher(sam));
+        conf.addEventMetric(EventMetric.newBuilder()
+                .setId(eventName.hashCode())
+                .setWhat(atomName.hashCode()));
+    }
+
+    /**
+     * Adds an atom to a gauge metric of a config
+     *
+     * @param conf        configuration
+     * @param atomId      atom id (from atoms.proto)
+     * @param gaugeMetric the gauge metric to add
+     */
+    protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId,
+            GaugeMetric.Builder gaugeMetric) throws Exception {
+        final String atomName = "Atom" + System.nanoTime();
+        final String gaugeName = "Gauge" + System.nanoTime();
+        final String predicateName = "APP_BREADCRUMB";
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+        conf.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(atomName.hashCode())
+                .setSimpleAtomMatcher(sam));
+        final String predicateTrueName = "APP_BREADCRUMB_1";
+        final String predicateFalseName = "APP_BREADCRUMB_2";
+        conf.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(predicateTrueName.hashCode())
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
+                                .setEqInt(1)
+                        )
+                )
+        )
+                // Used to trigger predicate
+                .addAtomMatcher(AtomMatcher.newBuilder()
+                        .setId(predicateFalseName.hashCode())
+                        .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
+                                        .setEqInt(2)
+                                )
+                        )
+                );
+        conf.addPredicate(Predicate.newBuilder()
+                .setId(predicateName.hashCode())
+                .setSimplePredicate(SimplePredicate.newBuilder()
+                        .setStart(predicateTrueName.hashCode())
+                        .setStop(predicateFalseName.hashCode())
+                        .setCountNesting(false)
+                )
+        );
+        gaugeMetric
+                .setId(gaugeName.hashCode())
+                .setWhat(atomName.hashCode())
+                .setCondition(predicateName.hashCode());
+        conf.addGaugeMetric(gaugeMetric.build());
+    }
+
+    /**
+     * Adds an atom to a gauge metric of a config
+     *
+     * @param conf      configuration
+     * @param atomId    atom id (from atoms.proto)
+     * @param dimension dimension is needed for most pulled atoms
+     */
+    protected void addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId,
+            @Nullable FieldMatcher.Builder dimension) throws Exception {
+        GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
+                .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
+                .setSamplingType(GaugeMetric.SamplingType.CONDITION_CHANGE_TO_TRUE)
+                .setMaxNumGaugeAtomsPerBucket(10000)
+                .setBucket(TimeUnit.CTS);
+        if (dimension != null) {
+            gaugeMetric.setDimensionsInWhat(dimension.build());
+        }
+        addGaugeAtom(conf, atomId, gaugeMetric);
+    }
+
+    /**
+     * Asserts that each set of states in stateSets occurs at least once in data.
+     * Asserts that the states in data occur in the same order as the sets in stateSets.
+     *
+     * @param stateSets        A list of set of states, where each set represents an equivalent
+     *                         state of the device for the purpose of CTS.
+     * @param data             list of EventMetricData from statsd, produced by
+     *                         getReportMetricListData()
+     * @param wait             expected duration (in ms) between state changes; asserts that the
+     *                         actual wait
+     *                         time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
+     *                         assertion.
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data,
+            int wait, Function<Atom, Integer> getStateFromAtom) {
+        // Sometimes, there are more events than there are states.
+        // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
+        assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
+        int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
+        for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
+            Atom atom = data.get(dataIndex).getAtom();
+            int state = getStateFromAtom.apply(atom);
+            // If state is in the current state set, we do not assert anything.
+            // If it is not, we expect to have transitioned to the next state set.
+            if (stateSets.get(stateSetIndex).contains(state)) {
+                // No need to assert anything. Just log it.
+                LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
+                        + "in stateSetIndex " + stateSetIndex + ":\n"
+                        + data.get(dataIndex).getAtom().toString());
+            } else {
+                stateSetIndex += 1;
+                LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
+                        + " in stateSetIndex " + stateSetIndex + ":\n"
+                        + data.get(dataIndex).getAtom().toString());
+                assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
+                assertWithMessage("Too many states").that(stateSetIndex)
+                    .isLessThan(stateSets.size());
+                assertWithMessage(String.format("Is in wrong state (%d)", state))
+                    .that(stateSets.get(stateSetIndex)).contains(state);
+                if (wait > 0) {
+                    assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
+                            wait / 2, wait * 5);
+                }
+            }
+        }
+        assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
+    }
+
+    /**
+     * Removes all elements from data prior to the first occurrence of an element of state. After
+     * this method is called, the first element of data (if non-empty) is guaranteed to be an
+     * element in state.
+     *
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public void popUntilFind(List<EventMetricData> data, Set<Integer> state,
+            Function<Atom, Integer> getStateFromAtom) {
+        int firstStateIdx;
+        for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
+            Atom atom = data.get(firstStateIdx).getAtom();
+            if (state.contains(getStateFromAtom.apply(atom))) {
+                break;
+            }
+        }
+        if (firstStateIdx == 0) {
+            // First first element already is in state, so there's nothing to do.
+            return;
+        }
+        data.subList(0, firstStateIdx).clear();
+    }
+
+    /**
+     * Removes all elements from data after to the last occurrence of an element of state. After
+     * this method is called, the last element of data (if non-empty) is guaranteed to be an
+     * element in state.
+     *
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public void popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state,
+        Function<Atom, Integer> getStateFromAtom) {
+        int lastStateIdx;
+        for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
+            Atom atom = data.get(lastStateIdx).getAtom();
+            if (state.contains(getStateFromAtom.apply(atom))) {
+                break;
+            }
+        }
+        if (lastStateIdx == data.size()-1) {
+            // Last element already is in state, so there's nothing to do.
+            return;
+        }
+        data.subList(lastStateIdx+1, data.size()).clear();
+    }
+
+    /** Returns the UID of the host, which should always either be SHELL (2000) or ROOT (0). */
+    protected int getHostUid() throws DeviceNotAvailableException {
+        String strUid = "";
+        try {
+            strUid = getDevice().executeShellCommand("id -u");
+            return Integer.parseInt(strUid.trim());
+        } catch (NumberFormatException e) {
+            LogUtil.CLog.e("Failed to get host's uid via shell command. Found " + strUid);
+            // Fall back to alternative method...
+            if (getDevice().isAdbRoot()) {
+                return 0; // ROOT
+            } else {
+                return 2000; // SHELL
+            }
+        }
+    }
+
+    protected String getProperty(String prop) throws Exception {
+        return getDevice().executeShellCommand("getprop " + prop).replace("\n", "");
+    }
+
+    protected void turnScreenOn() throws Exception {
+        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        getDevice().executeShellCommand("wm dismiss-keyguard");
+    }
+
+    protected void turnScreenOff() throws Exception {
+        getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
+    }
+
+    protected void setChargingState(int state) throws Exception {
+        getDevice().executeShellCommand("cmd battery set status " + state);
+    }
+
+    protected void unplugDevice() throws Exception {
+        // On batteryless devices on Android P or above, the 'unplug' command
+        // alone does not simulate the really unplugged state.
+        //
+        // This is because charging state is left as "unknown". Unless a valid
+        // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
+        // framework does not consider the device as running on battery.
+        setChargingState(3);
+
+        getDevice().executeShellCommand("cmd battery unplug");
+    }
+
+    protected void plugInAc() throws Exception {
+        getDevice().executeShellCommand("cmd battery set ac 1");
+    }
+
+    protected void plugInUsb() throws Exception {
+        getDevice().executeShellCommand("cmd battery set usb 1");
+    }
+
+    protected void plugInWireless() throws Exception {
+        getDevice().executeShellCommand("cmd battery set wireless 1");
+    }
+
+    protected void enableLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats enable");
+    }
+
+    protected void resetLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats reset");
+    }
+
+    protected void disableLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats disable");
+    }
+
+    protected void enableBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --enable");
+    }
+
+    protected void resetBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --reset");
+    }
+
+    protected void disableBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --disable");
+    }
+
+    protected void binderStatsNoSampling() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling");
+    }
+
+    protected void setUpLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats enable");
+        getDevice().executeShellCommand("cmd looper_stats sampling_interval 1");
+        getDevice().executeShellCommand("cmd looper_stats reset");
+    }
+
+    protected void cleanUpLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats disable");
+    }
+
+    public void setAppBreadcrumbPredicate() throws Exception {
+        doAppBreadcrumbReportedStart(1);
+    }
+
+    public void clearAppBreadcrumbPredicate() throws Exception {
+        doAppBreadcrumbReportedStart(2);
+    }
+
+    public void doAppBreadcrumbReportedStart(int label) throws Exception {
+        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal());
+    }
+
+    public void doAppBreadcrumbReportedStop(int label) throws Exception {
+        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal());
+    }
+
+    public void doAppBreadcrumbReported(int label) throws Exception {
+        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
+    }
+
+    public void doAppBreadcrumbReported(int label, int state) throws Exception {
+        getDevice().executeShellCommand(String.format(
+                "cmd stats log-app-breadcrumb %d %d", label, state));
+    }
+
+    protected void setBatteryLevel(int level) throws Exception {
+        getDevice().executeShellCommand("cmd battery set level " + level);
+    }
+
+    protected void resetBatteryStatus() throws Exception {
+        getDevice().executeShellCommand("cmd battery reset");
+    }
+
+    protected int getScreenBrightness() throws Exception {
+        return Integer.parseInt(
+                getDevice().executeShellCommand("settings get system screen_brightness").trim());
+    }
+
+    protected void setScreenBrightness(int brightness) throws Exception {
+        getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
+    }
+
+    // Gets whether "Always on Display" setting is enabled.
+    // In rare cases, this is different from whether the device can enter SCREEN_STATE_DOZE.
+    protected String getAodState() throws Exception {
+        return getDevice().executeShellCommand("settings get secure doze_always_on");
+    }
+
+    protected void setAodState(String state) throws Exception {
+        getDevice().executeShellCommand("settings put secure doze_always_on " + state);
+    }
+
+    protected boolean isScreenBrightnessModeManual() throws Exception {
+        String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode");
+        return Integer.parseInt(mode.trim()) == 0;
+    }
+
+    protected void setScreenBrightnessMode(boolean manual) throws Exception {
+        getDevice().executeShellCommand(
+                "settings put system screen_brightness_mode " + (manual ? 0 : 1));
+    }
+
+    protected void enterDozeModeLight() throws Exception {
+        getDevice().executeShellCommand("dumpsys deviceidle force-idle light");
+    }
+
+    protected void enterDozeModeDeep() throws Exception {
+        getDevice().executeShellCommand("dumpsys deviceidle force-idle deep");
+    }
+
+    protected void leaveDozeMode() throws Exception {
+        getDevice().executeShellCommand("dumpsys deviceidle unforce");
+        getDevice().executeShellCommand("dumpsys deviceidle disable");
+        getDevice().executeShellCommand("dumpsys deviceidle enable");
+    }
+
+    protected void turnBatterySaverOn() throws Exception {
+        unplugDevice();
+        getDevice().executeShellCommand("settings put global low_power 1");
+    }
+
+    protected void turnBatterySaverOff() throws Exception {
+        getDevice().executeShellCommand("settings put global low_power 0");
+        getDevice().executeShellCommand("cmd battery reset");
+    }
+
+    protected void rebootDevice() throws Exception {
+        getDevice().rebootUntilOnline();
+    }
+
+    /**
+     * Asserts that the two events are within the specified range of each other.
+     *
+     * @param d0        the event that should occur first
+     * @param d1        the event that should occur second
+     * @param minDiffMs d0 should precede d1 by at least this amount
+     * @param maxDiffMs d0 should precede d1 by at most this amount
+     */
+    public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
+            int minDiffMs, int maxDiffMs) {
+        long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
+        assertWithMessage("Illegal time difference")
+            .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
+    }
+
+    protected String getCurrentLogcatDate() throws Exception {
+        // TODO: Do something more robust than this for getting logcat markers.
+        long timestampMs = getDevice().getDeviceDate();
+        return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
+                .format(new Date(timestampMs));
+    }
+
+    protected String getLogcatSince(String date, String logcatParams) throws Exception {
+        return getDevice().executeShellCommand(String.format(
+                "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
+    }
+
+    // TODO: Remove this and migrate all usages to createConfigBuilder()
+    protected StatsdConfig.Builder getPulledConfig() {
+        return createConfigBuilder();
+    }
+    /**
+     * Determines if the device has the given feature.
+     * Prints a warning if its value differs from requiredAnswer.
+     */
+    protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
+        final String features = getDevice().executeShellCommand("pm list features");
+        boolean hasIt = features.contains(featureName);
+        if (hasIt != requiredAnswer) {
+            LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
+                    + featureName);
+        }
+        return hasIt == requiredAnswer;
+    }
+
+    /**
+     * Determines if the device has |file|.
+     */
+    protected boolean doesFileExist(String file) throws Exception {
+        return getDevice().doesFileExist(file);
+    }
+
+    protected void turnOnAirplaneMode() throws Exception {
+        getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
+    }
+
+    protected void turnOffAirplaneMode() throws Exception {
+        getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
+    }
+
+    /**
+     * Returns a list of fields and values for {@code className} from {@link TelephonyDebugService}
+     * output.
+     *
+     * <p>Telephony dumpsys output does not support proto at the moment. This method provides
+     * limited support for parsing its output. Specifically, it does not support arrays or
+     * multi-line values.
+     */
+    private List<Map<String, String>> getTelephonyDumpEntries(String className) throws Exception {
+        // Matches any line with indentation, except for lines with only spaces
+        Pattern indentPattern = Pattern.compile("^(\\s*)[^ ].*$");
+        // Matches pattern for class, e.g. "    Phone:"
+        Pattern classNamePattern = Pattern.compile("^(\\s*)" + Pattern.quote(className) + ":.*$");
+        // Matches pattern for key-value pairs, e.g. "     mPhoneId=1"
+        Pattern keyValuePattern = Pattern.compile("^(\\s*)([a-zA-Z]+[a-zA-Z0-9_]*)\\=(.+)$");
+        String response =
+                getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService");
+        Queue<String> responseLines = new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+")));
+
+        List<Map<String, String>> results = new ArrayList<>();
+        while (responseLines.peek() != null) {
+            Matcher matcher = classNamePattern.matcher(responseLines.poll());
+            if (matcher.matches()) {
+                final int classIndentLevel = matcher.group(1).length();
+                final Map<String, String> instanceEntries = new HashMap<>();
+                while (responseLines.peek() != null) {
+                    // Skip blank lines
+                    matcher = indentPattern.matcher(responseLines.peek());
+                    if (responseLines.peek().length() == 0 || !matcher.matches()) {
+                        responseLines.poll();
+                        continue;
+                    }
+                    // Finish (without consuming the line) if already parsed past this instance
+                    final int indentLevel = matcher.group(1).length();
+                    if (indentLevel <= classIndentLevel) {
+                        break;
+                    }
+                    // Parse key-value pair if it belongs to the instance directly
+                    matcher = keyValuePattern.matcher(responseLines.poll());
+                    if (indentLevel == classIndentLevel + 1 && matcher.matches()) {
+                        instanceEntries.put(matcher.group(2), matcher.group(3));
+                    }
+                }
+                results.add(instanceEntries);
+            }
+        }
+        return results;
+    }
+
+    protected int getActiveSimSlotCount() throws Exception {
+        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
+        long count = slots.stream().filter(slot -> "true".equals(slot.get("mActive"))).count();
+        return Math.toIntExact(count);
+    }
+
+    /**
+     * Returns the upper bound of active SIM profile count.
+     *
+     * <p>The value is an upper bound as eSIMs without profiles are also counted in.
+     */
+    protected int getActiveSimCountUpperBound() throws Exception {
+        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
+        long count = slots.stream().filter(slot ->
+                "true".equals(slot.get("mActive"))
+                && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))).count();
+        return Math.toIntExact(count);
+    }
+
+    /**
+     * Returns the upper bound of active eSIM profile count.
+     *
+     * <p>The value is an upper bound as eSIMs without profiles are also counted in.
+     */
+    protected int getActiveEsimCountUpperBound() throws Exception {
+        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
+        long count = slots.stream().filter(slot ->
+                "true".equals(slot.get("mActive"))
+                && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))
+                && "true".equals(slot.get("mIsEuicc"))).count();
+        return Math.toIntExact(count);
+    }
+
+    protected boolean hasGsmPhone() throws Exception {
+        // Not using log entries or ServiceState in the dump since they may or may not be present,
+        // which can make the test flaky
+        return getTelephonyDumpEntries("Phone").stream()
+                .anyMatch(phone ->
+                        String.format("%d", PHONE_TYPE_GSM).equals(phone.get("getPhoneType()")));
+    }
+
+    protected boolean hasCdmaPhone() throws Exception {
+        // Not using log entries or ServiceState in the dump due to the same reason as hasGsmPhone()
+        return getTelephonyDumpEntries("Phone").stream()
+                .anyMatch(phone ->
+                        String.format("%d", PHONE_TYPE_CDMA).equals(phone.get("getPhoneType()"))
+                        || String.format("%d", PHONE_TYPE_CDMA_LTE)
+                                .equals(phone.get("getPhoneType()")));
+    }
+
+    // Checks that a timestamp has been truncated to be a multiple of 5 min
+    protected void assertTimestampIsTruncated(long timestampNs) {
+        long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
+        assertWithMessage("Timestamp is not truncated")
+                .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BaseTestCase.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BaseTestCase.java
new file mode 100644
index 0000000..64d33ee
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BaseTestCase.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.statsd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+import java.io.FileNotFoundException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+// Largely copied from incident's ProtoDumpTestCase
+public class BaseTestCase extends DeviceTestCase implements IBuildReceiver {
+
+    protected IBuildInfo mCtsBuild;
+
+    private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public IBuildInfo getBuild() {
+        return mCtsBuild;
+    }
+
+    /**
+     * Call onto the device with an adb shell command and get the results of
+     * that as a proto of the given type.
+     *
+     * @param parser A protobuf parser object. e.g. MyProto.parser()
+     * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto"
+     *
+     * @throws DeviceNotAvailableException If there was a problem communicating with
+     *      the test device.
+     * @throws InvalidProtocolBufferException If there was an error parsing
+     *      the proto. Note that a 0 length buffer is not necessarily an error.
+     */
+    public <T extends MessageLite> T getDump(Parser<T> parser, String command)
+            throws DeviceNotAvailableException, InvalidProtocolBufferException {
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        getDevice().executeShellCommand(command, receiver);
+        if (false) {
+            CLog.d("Command output while parsing " + parser.getClass().getCanonicalName()
+                    + " for command: " + command + "\n"
+                    + BufferDebug.debugString(receiver.getOutput(), -1));
+        }
+        try {
+            return parser.parseFrom(receiver.getOutput());
+        } catch (Exception ex) {
+            CLog.d("Error parsing " + parser.getClass().getCanonicalName() + " for command: "
+                    + command
+                    + BufferDebug.debugString(receiver.getOutput(), 16384));
+            throw ex;
+        }
+    }
+
+    /**
+     * Install a device side test package.
+     *
+     * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
+     * @param grantPermissions whether to give runtime permissions.
+     */
+    protected void installPackage(String appFileName, boolean grantPermissions)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        CLog.d("Installing app " + appFileName);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        final String result = getDevice().installPackage(
+                buildHelper.getTestFile(appFileName), true, grantPermissions);
+        assertWithMessage(String.format("Failed to install %s: %s", appFileName, result))
+            .that(result).isNull();
+    }
+
+    protected CompatibilityBuildHelper getBuildHelper() {
+        return new CompatibilityBuildHelper(mCtsBuild);
+    }
+
+    /**
+     * Run a device side test.
+     *
+     * @param pkgName Test package name, such as "com.android.server.cts.netstats".
+     * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
+     * @param testMethodName Test method name.
+     * @return {@link TestRunResult} of this invocation.
+     * @throws DeviceNotAvailableException
+     */
+    @Nonnull
+    protected TestRunResult runDeviceTests(@Nonnull String pkgName,
+            @Nullable String testClassName, @Nullable String testMethodName)
+            throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+                pkgName, TEST_RUNNER, getDevice().getIDevice());
+        if (testClassName != null && testMethodName != null) {
+            testRunner.setMethodName(testClassName, testMethodName);
+        } else if (testClassName != null) {
+            testRunner.setClassName(testClassName);
+        }
+
+        CollectingTestListener listener = new CollectingTestListener();
+        assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
+
+        final TestRunResult result = listener.getCurrentRunResults();
+        if (result.isRunFailure()) {
+            throw new Error("Failed to successfully run device tests for "
+                    + result.getName() + ": " + result.getRunFailureMessage());
+        }
+        if (result.getNumTests() == 0) {
+            throw new Error("No tests were run on the device");
+        }
+
+        if (result.hasFailedTests()) {
+            // build a meaningful error message
+            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
+                    result.getTestResults().entrySet()) {
+                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+                    errorBuilder.append(resultEntry.getKey().toString());
+                    errorBuilder.append(":\n");
+                    errorBuilder.append(resultEntry.getValue().getStackTrace());
+                }
+            }
+            throw new AssertionError(errorBuilder.toString());
+        }
+
+        return result;
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BufferDebug.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BufferDebug.java
new file mode 100644
index 0000000..ae85bb3
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BufferDebug.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.statsd;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Formatter;
+
+/**
+ * Print utility for byte[].
+ */
+public class BufferDebug {
+    private static final int HALF_WIDTH = 8;
+
+    /**
+     * Number of bytes represented per row in hex output.
+     */
+    public static final int WIDTH = HALF_WIDTH * 2;
+
+    /**
+     * Return a string suitable for debugging.
+     * - If the byte is printable as an ascii string, return that, in quotation marks,
+     *   with a newline at the end.
+     * - Otherwise, return the hexdump -C style output.
+     *
+     * @param buf the buffer
+     * @param max print up to _max_ bytes, or the length of the string. If max is 0,
+     *      print the whole contents of buf.
+     */
+    public static String debugString(byte[] buf, int max) {
+        if (buf == null) {
+            return "(null)";
+        }
+        if (buf.length == 0) {
+            return "(length 0)";
+        }
+
+        int len = max;
+        if (len <= 0 || len > buf.length) {
+            max = len = buf.length;
+        }
+
+        if (isPrintable(buf, len)) {
+            return "\"" + new String(buf, 0, len, StandardCharsets.UTF_8) + "\"\n";
+        } else {
+            return toHex(buf, len, max);
+        }
+    }
+
+    private static String toHex(byte[] buf, int len, int max) {
+        final StringBuilder str = new StringBuilder();
+
+        // All but the last row
+        int rows = len / WIDTH;
+        for (int row = 0; row < rows; row++) {
+            writeRow(str, buf, row * WIDTH, WIDTH, max);
+        }
+
+        // Last row
+        if (len % WIDTH != 0) {
+            writeRow(str, buf, rows * WIDTH, max - (rows * WIDTH), max);
+        }
+
+        // Final len
+        str.append(String.format("%10d 0x%08x  ", buf.length, buf.length));
+        if (buf.length != max) {
+            str.append(String.format("truncated to %d 0x%08x", max, max));
+        }
+        str.append('\n');
+
+        return str.toString();
+    }
+
+    private static void writeRow(StringBuilder str, byte[] buf, int start, int len, int max) {
+        final Formatter f = new Formatter(str);
+
+        // Start index
+        f.format("%10d 0x%08x  ", start, start);
+
+        // One past the last char we will print
+        int end = start + len;
+        // Number of missing caracters due to this being the last line.
+        int padding = 0;
+        if (start + WIDTH > max) {
+            padding = WIDTH - (end % WIDTH);
+            end = max;
+        }
+
+        // Hex
+        for (int i = start; i < end; i++) {
+            f.format("%02x ", buf[i]);
+            if (i == start + HALF_WIDTH - 1) {
+                str.append(" ");
+            }
+        }
+        for (int i = 0; i < padding; i++) {
+            str.append("   ");
+        }
+        if (padding >= HALF_WIDTH) {
+            str.append(" ");
+        }
+
+        str.append("  ");
+        for (int i = start; i < end; i++) {
+            byte b = buf[i];
+            if (isPrintable(b)) {
+                str.append((char)b);
+            } else {
+                str.append('.');
+            }
+            if (i == start + HALF_WIDTH - 1) {
+                str.append("  ");
+            }
+        }
+
+        str.append('\n');
+    }
+
+    private static boolean isPrintable(byte[] buf, int len) {
+        for (int i=0; i<len; i++) {
+            if (!isPrintable(buf[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean isPrintable(byte c) {
+        return c >= 0x20 && c <= 0x7e;
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/DeviceAtomTestCase.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/DeviceAtomTestCase.java
new file mode 100644
index 0000000..b28a9f5
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/DeviceAtomTestCase.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsdatom.statsd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.MessageMatcher;
+import com.android.internal.os.StatsdConfigProto.Position;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.log.LogUtil;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app.
+ */
+public class DeviceAtomTestCase extends AtomTestCase {
+
+    public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
+    public static final String DEVICE_SIDE_TEST_PACKAGE =
+            "com.android.server.cts.device.statsd";
+    public static final long DEVICE_SIDE_TEST_PACKAGE_VERSION = 10;
+    public static final String DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME =
+            "com.android.server.cts.device.statsd.StatsdCtsForegroundService";
+    private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT =
+            "com.android.server.cts.device.statsd/.StatsdCtsBackgroundService";
+    public static final long DEVICE_SIDE_TEST_PKG_HASH =
+            Long.parseUnsignedLong("15694052924544098582");
+
+    // Constants from device side tests (not directly accessible here).
+    public static final String KEY_ACTION = "action";
+    public static final String ACTION_LMK = "action.lmk";
+
+    public static final String CONFIG_NAME = "cts_config";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        installTestApp();
+        Thread.sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    /**
+     * Performs a device-side test by calling a method on the app and returns its stats events.
+     * @param methodName the name of the method in the app's AtomTests to perform
+     * @param atom atom tag (from atoms.proto)
+     * @param key atom's field corresponding to state
+     * @param stateOn 'on' value
+     * @param stateOff 'off' value
+     * @param minTimeDiffMs max allowed time between start and stop
+     * @param maxTimeDiffMs min allowed time between start and stop
+     * @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop)
+     * @return list of events with the app's uid matching the configuration defined by the params.
+     */
+    protected List<EventMetricData> doDeviceMethodOnOff(
+            String methodName, int atom, int key, int stateOn, int stateOff,
+            int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOn));
+        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOff));
+        List<EventMetricData> data = doDeviceMethod(methodName, conf);
+
+        if (demandExactlyTwo) {
+            assertThat(data).hasSize(2);
+        } else {
+            assertThat(data.size()).isAtLeast(2);
+        }
+        assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
+        return data;
+    }
+
+    /**
+     *
+     * @param methodName the name of the method in the app's AtomTests to perform
+     * @param cfg statsd configuration
+     * @return list of events with the app's uid matching the configuration.
+     */
+    protected List<EventMetricData> doDeviceMethod(String methodName, StatsdConfig.Builder cfg)
+            throws Exception {
+        removeConfig(CONFIG_ID);
+        getReportList();  // Clears previous data on disk.
+        uploadConfig(cfg);
+        int appUid = getUid();
+        LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName);
+
+        return getEventMetricDataList();
+    }
+
+    protected void createAndUploadConfig(int atomTag, boolean useAttribution) throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atomTag, useAttribution);
+        uploadConfig(conf);
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the given key AND has the app's uid.
+     * @param conf configuration
+     * @param atomTag atom tag (from atoms.proto)
+     * @param fvm FieldValueMatcher.Builder for the relevant key
+     */
+    @Override
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)
+            throws Exception {
+
+        final int UID_KEY = 1;
+        FieldValueMatcher.Builder fvmUid = createAttributionFvm(UID_KEY);
+        addAtomEvent(conf, atomTag, Arrays.asList(fvm, fvmUid));
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the app's uid.
+     * @param conf configuration
+     * @param atomTag atom tag (from atoms.proto)
+     * @param useAttribution If true, the atom has a uid within an attribution node. Else, the atom
+     * has a uid but not in an attribution node.
+     */
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
+            boolean useAttribution) throws Exception {
+        final int UID_KEY = 1;
+        FieldValueMatcher.Builder fvmUid;
+        if (useAttribution) {
+            fvmUid = createAttributionFvm(UID_KEY);
+        } else {
+            fvmUid = createFvm(UID_KEY).setEqString(DEVICE_SIDE_TEST_PACKAGE);
+        }
+        addAtomEvent(conf, atomTag, Arrays.asList(fvmUid));
+    }
+
+    /**
+     * Creates a FieldValueMatcher for atoms that use AttributionNode
+     */
+    protected FieldValueMatcher.Builder createAttributionFvm(int field) {
+        final int ATTRIBUTION_NODE_UID_KEY = 1;
+        return createFvm(field).setPosition(Position.ANY)
+                .setMatchesTuple(MessageMatcher.newBuilder()
+                        .addFieldValueMatcher(createFvm(ATTRIBUTION_NODE_UID_KEY)
+                                .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
+    }
+
+    /**
+     * Gets the uid of the test app.
+     */
+    protected int getUid() throws Exception {
+        int currentUser = getDevice().getCurrentUser();
+        String uidLine = getDevice().executeShellCommand("cmd package list packages -U --user "
+                + currentUser + " " + DEVICE_SIDE_TEST_PACKAGE);
+        String[] uidLineParts = uidLine.split(":");
+        // 3rd entry is package uid
+        assertThat(uidLineParts.length).isGreaterThan(2);
+        int uid = Integer.parseInt(uidLineParts[2].trim());
+        assertThat(uid).isGreaterThan(10000);
+        return uid;
+    }
+
+    /**
+     * Installs the test apk.
+     */
+    protected void installTestApp() throws Exception {
+        installPackage(DEVICE_SIDE_TEST_APK, true);
+        LogUtil.CLog.i("Installing device-side test app with uid " + getUid());
+        allowBackgroundServices();
+    }
+
+    /**
+     * Uninstalls the test apk.
+     */
+    protected void uninstallPackage() throws Exception{
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+    }
+
+    /**
+     * Required to successfully start a background service from adb in O.
+     */
+    protected void allowBackgroundServices() throws Exception {
+        getDevice().executeShellCommand(String.format(
+                "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
+    }
+
+    /**
+     * Runs a (background) service to perform the given action.
+     * @param actionValue the action code constants indicating the desired action to perform.
+     */
+    protected void executeBackgroundService(String actionValue) throws Exception {
+        allowBackgroundServices();
+        getDevice().executeShellCommand(String.format(
+                "am startservice -n '%s' -e %s %s",
+                DEVICE_SIDE_BG_SERVICE_COMPONENT,
+                KEY_ACTION, actionValue));
+    }
+
+
+    /** Make the test app standby-active so it can run syncs and jobs immediately. */
+    protected void allowImmediateSyncs() throws Exception {
+        getDevice().executeShellCommand("am set-standby-bucket "
+                + DEVICE_SIDE_TEST_PACKAGE + " active");
+    }
+
+    /**
+     * Runs the specified activity.
+     */
+    protected void runActivity(String activity, String actionKey, String actionValue)
+            throws Exception {
+        runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG);
+    }
+
+    /**
+     * Runs the specified activity.
+     */
+    protected void runActivity(String activity, String actionKey, String actionValue,
+            long waitTime) throws Exception {
+        try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) {
+            Thread.sleep(waitTime);
+        }
+    }
+
+    /**
+     * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
+     * when closed.
+     *
+     * <p>Example usage:
+     * <pre>
+     *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
+     *         doStuff();
+     *     }
+     * </pre>
+     */
+    protected AutoCloseable withActivity(String activity, String actionKey, String actionValue)
+            throws Exception {
+        String intentString = null;
+        if (actionKey != null && actionValue != null) {
+            intentString = actionKey + " " + actionValue;
+        }
+        if (intentString == null) {
+            getDevice().executeShellCommand(
+                    "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity);
+        } else {
+            getDevice().executeShellCommand(
+                    "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " +
+                            intentString);
+        }
+        return () -> {
+            getDevice().executeShellCommand(
+                    "am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
+            Thread.sleep(WAIT_TIME_SHORT);
+        };
+    }
+
+    protected void resetBatteryStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys batterystats --reset");
+    }
+
+    protected void clearProcStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --clear");
+    }
+
+    protected void startProcStatsTesting() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --start-testing");
+    }
+
+    protected void stopProcStatsTesting() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --stop-testing");
+    }
+
+    protected void commitProcStatsToDisk() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --commit");
+    }
+
+    protected void rebootDeviceAndWaitUntilReady() throws Exception {
+        rebootDevice();
+        // Wait for 2 mins.
+        assertWithMessage("Device failed to boot")
+            .that(getDevice().waitForBootComplete(120_000)).isTrue();
+        assertWithMessage("Stats service failed to start")
+            .that(waitForStatsServiceStart(60_000)).isTrue();
+        Thread.sleep(2_000);
+    }
+
+    protected boolean waitForStatsServiceStart(final long waitTime) throws Exception {
+        LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime);
+        int counter = 1;
+        long startTime = System.currentTimeMillis();
+        while ((System.currentTimeMillis() - startTime) < waitTime) {
+            if ("running".equals(getProperty("init.svc.statsd"))) {
+                return true;
+            }
+            Thread.sleep(Math.min(200 * counter, 2_000));
+            counter++;
+        }
+        LogUtil.CLog.w("Stats service did not start after %d ms", waitTime);
+        return false;
+    }
+
+    boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception {
+        final String output = getDevice().executeShellCommand(
+                "settings get global netstats_combine_subtype_enabled").trim();
+        return output.equals("1");
+    }
+
+    void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
+        getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
+                + (enable ? "1" : "0"));
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java
new file mode 100644
index 0000000..a1c8119
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java
@@ -0,0 +1,699 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsdatom.statsd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.BatteryPluggedStateEnum;
+import android.os.BatteryStatusEnum;
+import android.platform.test.annotations.RestrictedBuildTest;
+import android.server.DeviceIdleModeEnum;
+import android.view.DisplayStateEnum;
+import android.telephony.NetworkTypeEnum;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.BatterySaverModeStateChanged;
+import com.android.os.AtomsProto.BuildInformation;
+import com.android.os.AtomsProto.ConnectivityStateChanged;
+import com.android.os.AtomsProto.SimSlotState;
+import com.android.os.AtomsProto.SupportedRadioAccessFamily;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.EventMetricData;
+
+import com.google.common.collect.Range;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Statsd atom tests that are done via adb (hostside).
+ */
+public class HostAtomTests extends AtomTestCase {
+
+    private static final String TAG = "Statsd.HostAtomTests";
+
+    // Either file must exist to read kernel wake lock stats.
+    private static final String WAKE_LOCK_FILE = "/proc/wakelocks";
+    private static final String WAKE_SOURCES_FILE = "/d/wakeup_sources";
+
+    // Bitmask of radio access technologies that all GSM phones should at least partially support
+    protected static final long NETWORK_TYPE_BITMASK_GSM_ALL =
+            (1 << (NetworkTypeEnum.NETWORK_TYPE_GSM_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_GPRS_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_EDGE_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_UMTS_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSDPA_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSUPA_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSPA_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSPAP_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_TD_SCDMA_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_LTE_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_LTE_CA_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_NR_VALUE - 1));
+    // Bitmask of radio access technologies that all CDMA phones should at least partially support
+    protected static final long NETWORK_TYPE_BITMASK_CDMA_ALL =
+            (1 << (NetworkTypeEnum.NETWORK_TYPE_CDMA_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_1XRTT_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_EVDO_0_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_EVDO_A_VALUE - 1))
+            | (1 << (NetworkTypeEnum.NETWORK_TYPE_EHRPD_VALUE - 1));
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testScreenStateChangedAtom() throws Exception {
+        // Setup, make sure the screen is off and turn off AoD if it is on.
+        // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
+        // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
+        String aodState = getAodState();
+        setAodState("0");
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_SHORT);
+        turnScreenOff();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> screenOnStates = new HashSet<>(
+                Arrays.asList(DisplayStateEnum.DISPLAY_STATE_ON_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_ON_SUSPEND_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_VR_VALUE));
+        Set<Integer> screenOffStates = new HashSet<>(
+                Arrays.asList(DisplayStateEnum.DISPLAY_STATE_OFF_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_DOZE_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_DOZE_SUSPEND_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_UNKNOWN_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(screenOnStates, screenOffStates);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_LONG);
+        turnScreenOff();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+        // reset screen to on
+        turnScreenOn();
+        // Restores AoD to initial state.
+        setAodState(aodState);
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
+                atom -> atom.getScreenStateChanged().getState().getNumber());
+    }
+
+    public void testChargingStateChangedAtom() throws Exception {
+        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
+        // Setup, set charging state to full.
+        setChargingState(5);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.CHARGING_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> batteryUnknownStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_UNKNOWN_VALUE));
+        Set<Integer> batteryChargingStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_CHARGING_VALUE));
+        Set<Integer> batteryDischargingStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_DISCHARGING_VALUE));
+        Set<Integer> batteryNotChargingStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_NOT_CHARGING_VALUE));
+        Set<Integer> batteryFullStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_FULL_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(batteryUnknownStates, batteryChargingStates,
+                batteryDischargingStates, batteryNotChargingStates, batteryFullStates);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        setChargingState(1);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setChargingState(2);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setChargingState(3);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setChargingState(4);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setChargingState(5);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Unfreeze battery state after test
+        resetBatteryStatus();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getChargingStateChanged().getState().getNumber());
+    }
+
+    public void testPluggedStateChangedAtom() throws Exception {
+        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
+        // Setup, unplug device.
+        unplugDevice();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> unpluggedStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_NONE_VALUE));
+        Set<Integer> acStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_AC_VALUE));
+        Set<Integer> usbStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_USB_VALUE));
+        Set<Integer> wirelessStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_WIRELESS_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(acStates, unpluggedStates, usbStates,
+                unpluggedStates, wirelessStates, unpluggedStates);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        plugInAc();
+        Thread.sleep(WAIT_TIME_SHORT);
+        unplugDevice();
+        Thread.sleep(WAIT_TIME_SHORT);
+        plugInUsb();
+        Thread.sleep(WAIT_TIME_SHORT);
+        unplugDevice();
+        Thread.sleep(WAIT_TIME_SHORT);
+        plugInWireless();
+        Thread.sleep(WAIT_TIME_SHORT);
+        unplugDevice();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Unfreeze battery state after test
+        resetBatteryStatus();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getPluggedStateChanged().getState().getNumber());
+    }
+
+    public void testBatteryLevelChangedAtom() throws Exception {
+        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
+        // Setup, set battery level to full.
+        setBatteryLevel(100);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.BATTERY_LEVEL_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> batteryLow = new HashSet<>(Arrays.asList(2));
+        Set<Integer> battery25p = new HashSet<>(Arrays.asList(25));
+        Set<Integer> battery50p = new HashSet<>(Arrays.asList(50));
+        Set<Integer> battery75p = new HashSet<>(Arrays.asList(75));
+        Set<Integer> batteryFull = new HashSet<>(Arrays.asList(100));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(batteryLow, battery25p, battery50p,
+                battery75p, batteryFull);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        setBatteryLevel(2);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setBatteryLevel(25);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setBatteryLevel(50);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setBatteryLevel(75);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setBatteryLevel(100);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Unfreeze battery state after test
+        resetBatteryStatus();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getBatteryLevelChanged().getBatteryLevel());
+    }
+
+    public void testDeviceIdleModeStateChangedAtom() throws Exception {
+        // Setup, leave doze mode.
+        leaveDozeMode();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.DEVICE_IDLE_MODE_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> dozeOff = new HashSet<>(
+                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_OFF_VALUE));
+        Set<Integer> dozeLight = new HashSet<>(
+                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_LIGHT_VALUE));
+        Set<Integer> dozeDeep = new HashSet<>(
+                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_DEEP_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(dozeLight, dozeDeep, dozeOff);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        enterDozeModeLight();
+        Thread.sleep(WAIT_TIME_SHORT);
+        enterDozeModeDeep();
+        Thread.sleep(WAIT_TIME_SHORT);
+        leaveDozeMode();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();;
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getDeviceIdleModeStateChanged().getState().getNumber());
+    }
+
+    public void testBatterySaverModeStateChangedAtom() throws Exception {
+        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
+        // Setup, turn off battery saver.
+        turnBatterySaverOff();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.BATTERY_SAVER_MODE_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> batterySaverOn = new HashSet<>(
+                Arrays.asList(BatterySaverModeStateChanged.State.ON_VALUE));
+        Set<Integer> batterySaverOff = new HashSet<>(
+                Arrays.asList(BatterySaverModeStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(batterySaverOn, batterySaverOff);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        turnBatterySaverOn();
+        Thread.sleep(WAIT_TIME_LONG);
+        turnBatterySaverOff();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
+                atom -> atom.getBatterySaverModeStateChanged().getState().getNumber());
+    }
+
+    @RestrictedBuildTest
+    public void testRemainingBatteryCapacity() throws Exception {
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.REMAINING_BATTERY_CAPACITY_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        assertThat(data).isNotEmpty();
+        Atom atom = data.get(0);
+        assertThat(atom.getRemainingBatteryCapacity().hasChargeMicroAmpereHour()).isTrue();
+        if (hasBattery()) {
+            assertThat(atom.getRemainingBatteryCapacity().getChargeMicroAmpereHour())
+                .isGreaterThan(0);
+        }
+    }
+
+    @RestrictedBuildTest
+    public void testFullBatteryCapacity() throws Exception {
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.FULL_BATTERY_CAPACITY_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        assertThat(data).isNotEmpty();
+        Atom atom = data.get(0);
+        assertThat(atom.getFullBatteryCapacity().hasCapacityMicroAmpereHour()).isTrue();
+        if (hasBattery()) {
+            assertThat(atom.getFullBatteryCapacity().getCapacityMicroAmpereHour()).isGreaterThan(0);
+        }
+    }
+
+    public void testBatteryVoltage() throws Exception {
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.BATTERY_VOLTAGE_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        assertThat(data).isNotEmpty();
+        Atom atom = data.get(0);
+        assertThat(atom.getBatteryVoltage().hasVoltageMillivolt()).isTrue();
+        if (hasBattery()) {
+            assertThat(atom.getBatteryVoltage().getVoltageMillivolt()).isGreaterThan(0);
+        }
+    }
+
+    // This test is for the pulled battery level atom.
+    public void testBatteryLevel() throws Exception {
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.BATTERY_LEVEL_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        assertThat(data).isNotEmpty();
+        Atom atom = data.get(0);
+        assertThat(atom.getBatteryLevel().hasBatteryLevel()).isTrue();
+        if (hasBattery()) {
+            assertThat(atom.getBatteryLevel().getBatteryLevel()).isIn(Range.openClosed(0, 100));
+        }
+    }
+
+    // This test is for the pulled battery charge count atom.
+    public void testBatteryCycleCount() throws Exception {
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.BATTERY_CYCLE_COUNT_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        assertThat(data).isNotEmpty();
+        Atom atom = data.get(0);
+        assertThat(atom.getBatteryCycleCount().hasCycleCount()).isTrue();
+        if (hasBattery()) {
+            assertThat(atom.getBatteryCycleCount().getCycleCount()).isAtLeast(0);
+        }
+    }
+
+    public void testKernelWakelock() throws Exception {
+        if (!kernelWakelockStatsExist()) {
+            return;
+        }
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.KERNEL_WAKELOCK_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        assertThat(data).isNotEmpty();
+        for (Atom atom : data) {
+            assertThat(atom.getKernelWakelock().hasName()).isTrue();
+            assertThat(atom.getKernelWakelock().hasCount()).isTrue();
+            assertThat(atom.getKernelWakelock().hasVersion()).isTrue();
+            assertThat(atom.getKernelWakelock().getVersion()).isGreaterThan(0);
+            assertThat(atom.getKernelWakelock().hasTimeMicros()).isTrue();
+        }
+    }
+
+    // Returns true iff either |WAKE_LOCK_FILE| or |WAKE_SOURCES_FILE| exists.
+    private boolean kernelWakelockStatsExist() {
+      try {
+        return doesFileExist(WAKE_LOCK_FILE) || doesFileExist(WAKE_SOURCES_FILE);
+      } catch(Exception e) {
+        return false;
+      }
+    }
+
+    public void testWifiActivityInfo() throws Exception {
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        if (!checkDeviceFor("checkWifiEnhancedPowerReportingSupported")) return;
+
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.WIFI_ACTIVITY_INFO_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> dataList = getGaugeMetricDataList();
+
+        for (Atom atom: dataList) {
+            assertThat(atom.getWifiActivityInfo().getTimestampMillis()).isGreaterThan(0L);
+            assertThat(atom.getWifiActivityInfo().getStackState()).isAtLeast(0);
+            assertThat(atom.getWifiActivityInfo().getControllerIdleTimeMillis()).isGreaterThan(0L);
+            assertThat(atom.getWifiActivityInfo().getControllerTxTimeMillis()).isAtLeast(0L);
+            assertThat(atom.getWifiActivityInfo().getControllerRxTimeMillis()).isAtLeast(0L);
+            assertThat(atom.getWifiActivityInfo().getControllerEnergyUsed()).isAtLeast(0L);
+        }
+    }
+
+    public void testBuildInformation() throws Exception {
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.BUILD_INFORMATION_FIELD_NUMBER, null);
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+        assertThat(data).isNotEmpty();
+        BuildInformation atom = data.get(0).getBuildInformation();
+        assertThat(getProperty("ro.product.brand")).isEqualTo(atom.getBrand());
+        assertThat(getProperty("ro.product.name")).isEqualTo(atom.getProduct());
+        assertThat(getProperty("ro.product.device")).isEqualTo(atom.getDevice());
+        assertThat(getProperty("ro.build.version.release_or_codename")).isEqualTo(atom.getVersionRelease());
+        assertThat(getProperty("ro.build.id")).isEqualTo(atom.getId());
+        assertThat(getProperty("ro.build.version.incremental"))
+            .isEqualTo(atom.getVersionIncremental());
+        assertThat(getProperty("ro.build.type")).isEqualTo(atom.getType());
+        assertThat(getProperty("ro.build.tags")).isEqualTo(atom.getTags());
+    }
+
+    public void testOnDevicePowerMeasurement() throws Exception {
+        if (!OPTIONAL_TESTS_ENABLED) return;
+
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.ON_DEVICE_POWER_MEASUREMENT_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> dataList = getGaugeMetricDataList();
+
+        for (Atom atom: dataList) {
+            assertThat(atom.getOnDevicePowerMeasurement().getMeasurementTimestampMillis())
+                .isAtLeast(0L);
+            assertThat(atom.getOnDevicePowerMeasurement().getEnergyMicrowattSecs()).isAtLeast(0L);
+        }
+    }
+
+    // Explicitly tests if the adb command to log a breadcrumb is working.
+    public void testBreadcrumbAdb() throws Exception {
+        final int atomTag = Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        doAppBreadcrumbReportedStart(1);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
+        assertThat(atom.getLabel()).isEqualTo(1);
+        assertThat(atom.getState().getNumber()).isEqualTo(AppBreadcrumbReported.State.START_VALUE);
+    }
+
+    // Test dumpsys stats --proto.
+    public void testDumpsysStats() throws Exception {
+        final int atomTag = Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        doAppBreadcrumbReportedStart(1);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Get the stats incident section.
+        List<ConfigMetricsReportList> listList = getReportsFromStatsDataDumpProto();
+        assertThat(listList).isNotEmpty();
+
+        // Extract the relevant report from the incident section.
+        ConfigMetricsReportList ourList = null;
+        int hostUid = getHostUid();
+        for (ConfigMetricsReportList list : listList) {
+            ConfigMetricsReportList.ConfigKey configKey = list.getConfigKey();
+            if (configKey.getUid() == hostUid && configKey.getId() == CONFIG_ID) {
+                ourList = list;
+                break;
+            }
+        }
+        assertWithMessage(String.format("Could not find list for uid=%d id=%d", hostUid, CONFIG_ID))
+            .that(ourList).isNotNull();
+
+        // Make sure that the report is correct.
+        List<EventMetricData> data = getEventMetricDataList(ourList);
+        AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
+        assertThat(atom.getLabel()).isEqualTo(1);
+        assertThat(atom.getState().getNumber()).isEqualTo(AppBreadcrumbReported.State.START_VALUE);
+    }
+
+    public void testConnectivityStateChange() throws Exception {
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
+
+        final int atomTag = Atom.CONNECTIVITY_STATE_CHANGED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        turnOnAirplaneMode();
+        // wait long enough for airplane mode events to propagate.
+        Thread.sleep(1_200);
+        turnOffAirplaneMode();
+        // wait long enough for the device to restore connection
+        Thread.sleep(13_000);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        // at least 1 disconnect and 1 connect
+        assertThat(data.size()).isAtLeast(2);
+        boolean foundDisconnectEvent = false;
+        boolean foundConnectEvent = false;
+        for (EventMetricData d : data) {
+            ConnectivityStateChanged atom = d.getAtom().getConnectivityStateChanged();
+            if(atom.getState().getNumber()
+                    == ConnectivityStateChanged.State.DISCONNECTED_VALUE) {
+                foundDisconnectEvent = true;
+            }
+            if(atom.getState().getNumber()
+                    == ConnectivityStateChanged.State.CONNECTED_VALUE) {
+                foundConnectEvent = true;
+            }
+        }
+        assertThat(foundConnectEvent).isTrue();
+        assertThat(foundDisconnectEvent).isTrue();
+    }
+
+    public void testSimSlotState() throws Exception {
+        if (!hasFeature(FEATURE_TELEPHONY, true)) {
+            return;
+        }
+
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.SIM_SLOT_STATE_FIELD_NUMBER, null);
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+        assertThat(data).isNotEmpty();
+        SimSlotState atom = data.get(0).getSimSlotState();
+        // NOTE: it is possible for devices with telephony support to have no SIM at all
+        assertThat(atom.getActiveSlotCount()).isEqualTo(getActiveSimSlotCount());
+        assertThat(atom.getSimCount()).isAtMost(getActiveSimCountUpperBound());
+        assertThat(atom.getEsimCount()).isAtMost(getActiveEsimCountUpperBound());
+        // Above assertions do no necessarily enforce the following, since some are upper bounds
+        assertThat(atom.getActiveSlotCount()).isAtLeast(atom.getSimCount());
+        assertThat(atom.getSimCount()).isAtLeast(atom.getEsimCount());
+        assertThat(atom.getEsimCount()).isAtLeast(0);
+        // For GSM phones, at least one slot should be active even if there is no card
+        if (hasGsmPhone()) {
+            assertThat(atom.getActiveSlotCount()).isAtLeast(1);
+        }
+    }
+
+    public void testSupportedRadioAccessFamily() throws Exception {
+        if (!hasFeature(FEATURE_TELEPHONY, true)) {
+            return;
+        }
+
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.SUPPORTED_RADIO_ACCESS_FAMILY_FIELD_NUMBER, null);
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+        assertThat(data).isNotEmpty();
+        SupportedRadioAccessFamily atom = data.get(0).getSupportedRadioAccessFamily();
+        if (hasGsmPhone()) {
+            assertThat(atom.getNetworkTypeBitmask() & NETWORK_TYPE_BITMASK_GSM_ALL)
+                    .isNotEqualTo(0L);
+        }
+        if (hasCdmaPhone()) {
+            assertThat(atom.getNetworkTypeBitmask() & NETWORK_TYPE_BITMASK_CDMA_ALL)
+                    .isNotEqualTo(0L);
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/OWNERS
new file mode 100644
index 0000000..a716430
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/OWNERS
@@ -0,0 +1,8 @@
+# These atom tests are owned by the statsd team.
+jeffreyhuang@google.com
+jtnguyen@google.com
+muhammadq@google.com
+ruchirr@google.com
+singhtejinder@google.com
+tsaichristine@google.com
+yro@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java
new file mode 100644
index 0000000..9d93535
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsdatom.statsd;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.ProcessStateEnum; // From enums.proto for atoms.proto's UidProcessStateChanged.
+
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.EventMetricData;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Statsd atom tests that are done via app, for atoms that report a uid.
+ */
+public class ProcStateAtomTests extends ProcStateTestCase {
+
+    private static final String TAG = "Statsd.ProcStateAtomTests";
+
+    private static final int WAIT_TIME_FOR_CONFIG_UPDATE_MS = 200;
+    // ActivityManager can take a while to register screen state changes, mandating an extra delay.
+    private static final int WAIT_TIME_FOR_CONFIG_AND_SCREEN_MS = 1_000;
+    private static final int EXTRA_WAIT_TIME_MS = 5_000; // as buffer when proc state changing.
+    private static final int STATSD_REPORT_WAIT_TIME_MS = 500; // make sure statsd finishes log.
+
+    private static final String FEATURE_WATCH = "android.hardware.type.watch";
+
+    // The tests here are using the BatteryStats definition of 'background'.
+    private static final Set<Integer> BG_STATES = new HashSet<>(
+            Arrays.asList(
+                    ProcessStateEnum.PROCESS_STATE_IMPORTANT_BACKGROUND_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_TRANSIENT_BACKGROUND_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_BACKUP_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_SERVICE_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_RECEIVER_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_HEAVY_WEIGHT_VALUE
+            ));
+
+    // Using the BatteryStats definition of 'cached', which is why HOME (etc) are considered cached.
+    private static final Set<Integer> CACHED_STATES = new HashSet<>(
+            Arrays.asList(
+                    ProcessStateEnum.PROCESS_STATE_HOME_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_LAST_ACTIVITY_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_ACTIVITY_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_ACTIVITY_CLIENT_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_RECENT_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_EMPTY_VALUE
+            ));
+
+    private static final Set<Integer> MISC_STATES = new HashSet<>(
+            Arrays.asList(
+                    ProcessStateEnum.PROCESS_STATE_PERSISTENT_VALUE, // TODO: untested
+                    ProcessStateEnum.PROCESS_STATE_PERSISTENT_UI_VALUE, // TODO: untested
+                    ProcessStateEnum.PROCESS_STATE_TOP_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_BOUND_TOP_VALUE, // TODO: untested
+                    ProcessStateEnum.PROCESS_STATE_BOUND_FOREGROUND_SERVICE_VALUE, // TODO: untested
+                    ProcessStateEnum.PROCESS_STATE_FOREGROUND_SERVICE_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_IMPORTANT_FOREGROUND_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_TOP_SLEEPING_VALUE,
+
+                    ProcessStateEnum.PROCESS_STATE_UNKNOWN_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_NONEXISTENT_VALUE
+            ));
+
+    private static final Set<Integer> ALL_STATES = Stream.of(MISC_STATES, CACHED_STATES, BG_STATES)
+            .flatMap(s -> s.stream()).collect(Collectors.toSet());
+
+    private static final Function<Atom, Integer> PROC_STATE_FUNCTION =
+            atom -> atom.getUidProcessStateChanged().getState().getNumber();
+
+    private static final int PROC_STATE_ATOM_TAG = Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testForegroundService() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_FOREGROUND_SERVICE_VALUE));
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        executeForegroundService();
+        final int waitTime = SLEEP_OF_FOREGROUND_SERVICE;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS + EXTRA_WAIT_TIME_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
+        assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+    }
+
+    public void testForeground() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_IMPORTANT_FOREGROUND_VALUE));
+        // There are no offStates, since the app remains in foreground until killed.
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
+
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_AND_SCREEN_MS);
+
+        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+        final int waitTime = EXTRA_WAIT_TIME_MS + 5_000; // Overlay may need to sit there a while.
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
+        assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
+    }
+
+    public void testBackground() throws Exception {
+        Set<Integer> onStates = BG_STATES;
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
+        final int waitTime = SLEEP_OF_ACTION_BACKGROUND_SLEEP;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS + EXTRA_WAIT_TIME_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
+        assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+    }
+
+    public void testTop() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_TOP_VALUE));
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
+
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_AND_SCREEN_MS);
+
+        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
+        final int waitTime = SLEEP_OF_ACTION_SLEEP_WHILE_TOP;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS + EXTRA_WAIT_TIME_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
+        assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+    }
+
+    public void testTopSleeping() throws Exception {
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_TOP_SLEEPING_VALUE));
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  //False: does not use attribution.
+
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_AND_SCREEN_MS);
+
+        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
+        // ASAP, turn off the screen to make proc state -> top_sleeping.
+        turnScreenOff();
+        final int waitTime = SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        popUntilFind(data, new HashSet<>(Arrays.asList(ProcessStateEnum.PROCESS_STATE_TOP_VALUE)),
+                PROC_STATE_FUNCTION); // clear out anything prior to it entering TOP.
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out TOP itself.
+        // reset screen back on
+        turnScreenOn();
+        // Don't check the wait time, since it's up to the system how long top sleeping persists.
+        assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
+    }
+
+    public void testCached() throws Exception {
+        Set<Integer> onStates = CACHED_STATES;
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: des not use attribution.
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        // The schedule is as follows
+        // #1. The system may do anything it wants, such as moving the app into a cache state.
+        // #2. We move the app into the background.
+        // #3. The background process ends, so the app definitely moves to a cache state
+        //          (this is the ultimate goal of the test).
+        // #4. We start a foreground activity, moving the app out of cache.
+
+        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
+        executeBackgroundService(ACTION_END_IMMEDIATELY);
+        final int cacheTime = 2_000; // process should be in cached state for up to this long
+        Thread.sleep(cacheTime);
+        // Now forcibly bring the app out of cache (#4 above).
+        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+        // Now check the data *before* the app enters cache again (to avoid another cache event).
+
+        List<EventMetricData> data = getEventMetricDataList();
+        // First, clear out any incidental cached states of step #1, prior to step #2.
+        popUntilFind(data, BG_STATES, PROC_STATE_FUNCTION);
+        // Now clear out the bg state from step #2 (since we are interested in the cache after it).
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION);
+        // The result is that data should start at step #3, definitively in a cached state.
+        assertStatesOccurred(stateSet, data, 1_000, PROC_STATE_FUNCTION);
+    }
+
+    public void testValidityOfStates() throws Exception {
+        assertWithMessage("UNKNOWN_TO_PROTO should not be a valid state")
+            .that(ALL_STATES).doesNotContain(ProcessStateEnum.PROCESS_STATE_UNKNOWN_TO_PROTO_VALUE);
+    }
+
+    /** Returns the a set containing elements of a that are not elements of b. */
+    private Set<Integer> difference(Set<Integer> a, Set<Integer> b) {
+        Set<Integer> result = new HashSet<Integer>(a);
+        result.removeAll(b);
+        return result;
+    }
+
+    /** Returns the set of all states that are not in set. */
+    private Set<Integer> complement(Set<Integer> set) {
+        return difference(ALL_STATES, set);
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateTestCase.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateTestCase.java
new file mode 100644
index 0000000..ead14c6
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateTestCase.java
@@ -0,0 +1,76 @@
+/*
+ * 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.cts.statsdatom.statsd;
+
+import android.app.ProcessStateEnum; // From enums.proto for atoms.proto's UidProcessStateChanged.
+
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.EventMetricData;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Base class for manipulating process states
+ */
+public class ProcStateTestCase extends DeviceAtomTestCase {
+
+  private static final String TAG = "Statsd.ProcStateTestCase";
+
+  private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
+          = "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity";
+  private static final String DEVICE_SIDE_FG_SERVICE_COMPONENT
+          = "com.android.server.cts.device.statsd/.StatsdCtsForegroundService";
+
+  // Constants from the device-side tests (not directly accessible here).
+  public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
+  public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
+  public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
+  public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
+  public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
+
+  // Sleep times (ms) that actions invoke device-side.
+  public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
+  public static final int SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP = 60_000;
+  public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
+  public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
+
+
+  /**
+   * Runs an activity (in the foreground) to perform the given action.
+   * @param actionValue the action code constants indicating the desired action to perform.
+   */
+  protected void executeForegroundActivity(String actionValue) throws Exception {
+    getDevice().executeShellCommand(String.format(
+            "am start -n '%s' -e %s %s",
+            DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
+            KEY_ACTION, actionValue));
+  }
+
+  /**
+   * Runs a simple foreground service.
+   */
+  protected void executeForegroundService() throws Exception {
+    executeForegroundActivity(ACTION_END_IMMEDIATELY);
+    getDevice().executeShellCommand(String.format(
+            "am startservice -n '%s'", DEVICE_SIDE_FG_SERVICE_COMPONENT));
+  }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/SampleTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/SampleTests.java
new file mode 100644
index 0000000..87367e6
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/SampleTests.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.statsd;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.TestLibrary;
+import com.android.tradefed.testtype.DeviceTestCase;
+
+public final class SampleTests extends DeviceTestCase {
+    public void testFibonacciImplementation() throws Exception {
+        assertThat(TestLibrary.fib(1)).isEqualTo(1);
+        assertThat(TestLibrary.fib(2)).isEqualTo(1);
+        assertThat(TestLibrary.fib(3)).isEqualTo(2);
+        assertThat(TestLibrary.fib(4)).isEqualTo(3);
+        assertThat(TestLibrary.fib(5)).isEqualTo(5);
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
new file mode 100644
index 0000000..6ef6471
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
@@ -0,0 +1,2165 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsdatom.statsd;
+
+import static com.android.os.AtomsProto.IntegrityCheckResultReported.Response.ALLOWED;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.AppOpEnum;
+import android.net.wifi.WifiModeEnum;
+import android.os.WakeLockLevelEnum;
+import android.server.ErrorSource;
+import android.telephony.NetworkTypeEnum;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.PropertyUtil;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.ANROccurred;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.AppCrashOccurred;
+import com.android.os.AtomsProto.AppOps;
+import com.android.os.AtomsProto.AppStartOccurred;
+import com.android.os.AtomsProto.AppUsageEventOccurred;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.AttributedAppOps;
+import com.android.os.AtomsProto.AttributionNode;
+import com.android.os.AtomsProto.AudioStateChanged;
+import com.android.os.AtomsProto.BinderCalls;
+import com.android.os.AtomsProto.BleScanResultReceived;
+import com.android.os.AtomsProto.BleScanStateChanged;
+import com.android.os.AtomsProto.BlobCommitted;
+import com.android.os.AtomsProto.BlobLeased;
+import com.android.os.AtomsProto.BlobOpened;
+import com.android.os.AtomsProto.CameraStateChanged;
+import com.android.os.AtomsProto.DangerousPermissionState;
+import com.android.os.AtomsProto.DangerousPermissionStateSampled;
+import com.android.os.AtomsProto.DeviceCalculatedPowerBlameUid;
+import com.android.os.AtomsProto.FlashlightStateChanged;
+import com.android.os.AtomsProto.ForegroundServiceAppOpSessionEnded;
+import com.android.os.AtomsProto.ForegroundServiceStateChanged;
+import com.android.os.AtomsProto.GpsScanStateChanged;
+import com.android.os.AtomsProto.IntegrityCheckResultReported;
+import com.android.os.AtomsProto.IonHeapSize;
+import com.android.os.AtomsProto.LmkKillOccurred;
+import com.android.os.AtomsProto.LooperStats;
+import com.android.os.AtomsProto.MediaCodecStateChanged;
+import com.android.os.AtomsProto.NotificationReported;
+import com.android.os.AtomsProto.OverlayStateChanged;
+import com.android.os.AtomsProto.PackageNotificationChannelGroupPreferences;
+import com.android.os.AtomsProto.PackageNotificationChannelPreferences;
+import com.android.os.AtomsProto.PackageNotificationPreferences;
+import com.android.os.AtomsProto.PictureInPictureStateChanged;
+import com.android.os.AtomsProto.ProcessMemoryHighWaterMark;
+import com.android.os.AtomsProto.ProcessMemorySnapshot;
+import com.android.os.AtomsProto.ProcessMemoryState;
+import com.android.os.AtomsProto.ScheduledJobStateChanged;
+import com.android.os.AtomsProto.SettingSnapshot;
+import com.android.os.AtomsProto.SyncStateChanged;
+import com.android.os.AtomsProto.TestAtomReported;
+import com.android.os.AtomsProto.UiEventReported;
+import com.android.os.AtomsProto.VibratorStateChanged;
+import com.android.os.AtomsProto.WakelockStateChanged;
+import com.android.os.AtomsProto.WakeupAlarmOccurred;
+import com.android.os.AtomsProto.WifiLockStateChanged;
+import com.android.os.AtomsProto.WifiMulticastLockStateChanged;
+import com.android.os.AtomsProto.WifiScanStateChanged;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.server.notification.SmallHash;
+import com.android.tradefed.log.LogUtil;
+
+import com.google.common.collect.Range;
+import com.google.protobuf.Descriptors;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Statsd atom tests that are done via app, for atoms that report a uid.
+ */
+public class UidAtomTests extends DeviceAtomTestCase {
+
+    private static final String TAG = "Statsd.UidAtomTests";
+
+    private static final String TEST_PACKAGE_NAME = "com.android.server.cts.device.statsd";
+
+    private static final boolean DAVEY_ENABLED = false;
+
+    private static final int NUM_APP_OPS = AttributedAppOps.getDefaultInstance().getOp().
+            getDescriptorForType().getValues().size() - 1;
+
+    private static final String TEST_INSTALL_APK = "CtsStatsdEmptyApp.apk";
+    private static final String TEST_INSTALL_APK_BASE = "CtsStatsdEmptySplitApp.apk";
+    private static final String TEST_INSTALL_APK_SPLIT = "CtsStatsdEmptySplitApp_pl.apk";
+    private static final String TEST_INSTALL_PACKAGE =
+            "com.android.cts.device.statsd.emptyapp";
+    private static final String TEST_REMOTE_DIR = "/data/local/tmp/statsd";
+    private static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
+    private static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
+
+    private static final int WAIT_TIME_FOR_CONFIG_UPDATE_MS = 200;
+    private static final int EXTRA_WAIT_TIME_MS = 5_000; // as buffer when app starting/stopping.
+    private static final int STATSD_REPORT_WAIT_TIME_MS = 500; // make sure statsd finishes log.
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        resetBatteryStatus();
+        super.tearDown();
+    }
+
+    public void testLmkKillOccurred() throws Exception {
+        if (!"true".equals(getProperty("ro.lmk.log_stats"))) {
+            return;
+        }
+
+        final int atomTag = Atom.LMK_KILL_OCCURRED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag, false);
+
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        executeBackgroundService(ACTION_LMK);
+        Thread.sleep(15_000);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        assertThat(data).hasSize(1);
+        assertThat(data.get(0).getAtom().hasLmkKillOccurred()).isTrue();
+        LmkKillOccurred atom = data.get(0).getAtom().getLmkKillOccurred();
+        assertThat(atom.getUid()).isEqualTo(getUid());
+        assertThat(atom.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+        assertThat(atom.getOomAdjScore()).isAtLeast(500);
+    }
+
+    public void testAppCrashOccurred() throws Exception {
+        final int atomTag = Atom.APP_CRASH_OCCURRED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag, false);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runActivity("StatsdCtsForegroundActivity", "action", "action.crash");
+
+        Thread.sleep(WAIT_TIME_SHORT);
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        AppCrashOccurred atom = data.get(0).getAtom().getAppCrashOccurred();
+        assertThat(atom.getEventType()).isEqualTo("crash");
+        assertThat(atom.getIsInstantApp().getNumber())
+            .isEqualTo(AppCrashOccurred.InstantApp.FALSE_VALUE);
+        assertThat(atom.getForegroundState().getNumber())
+            .isEqualTo(AppCrashOccurred.ForegroundState.FOREGROUND_VALUE);
+        assertThat(atom.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+    }
+
+    public void testAppStartOccurred() throws Exception {
+        final int atomTag = Atom.APP_START_OCCURRED_FIELD_NUMBER;
+
+        createAndUploadConfig(atomTag, false);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runActivity("StatsdCtsForegroundActivity", "action", "action.sleep_top", 3_500);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        assertThat(data).hasSize(1);
+        AppStartOccurred atom = data.get(0).getAtom().getAppStartOccurred();
+        assertThat(atom.getPkgName()).isEqualTo(TEST_PACKAGE_NAME);
+        assertThat(atom.getActivityName())
+            .isEqualTo("com.android.server.cts.device.statsd.StatsdCtsForegroundActivity");
+        assertThat(atom.getIsInstantApp()).isFalse();
+        assertThat(atom.getActivityStartMillis()).isGreaterThan(0L);
+        assertThat(atom.getTransitionDelayMillis()).isGreaterThan(0);
+    }
+
+    public void testAudioState() throws Exception {
+        if (!hasFeature(FEATURE_AUDIO_OUTPUT, true)) return;
+
+        final int atomTag = Atom.AUDIO_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testAudioState";
+
+        Set<Integer> onState = new HashSet<>(
+                Arrays.asList(AudioStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Arrays.asList(AudioStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        Thread.sleep(WAIT_TIME_SHORT);
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Because the timestamp is truncated, we skip checking time differences between state
+        // changes.
+        assertStatesOccurred(stateSet, data, 0,
+                atom -> atom.getAudioStateChanged().getState().getNumber());
+
+        // Check that timestamp is truncated
+        for (EventMetricData metric : data) {
+            long elapsedTimestampNs = metric.getElapsedTimestampNanos();
+            assertTimestampIsTruncated(elapsedTimestampNs);
+        }
+    }
+
+    public void testBleScan() throws Exception {
+        if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
+
+        final int atom = Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER;
+        final int field = BleScanStateChanged.STATE_FIELD_NUMBER;
+        final int stateOn = BleScanStateChanged.State.ON_VALUE;
+        final int stateOff = BleScanStateChanged.State.OFF_VALUE;
+        final int minTimeDiffMillis = 1_500;
+        final int maxTimeDiffMillis = 3_000;
+
+        List<EventMetricData> data = doDeviceMethodOnOff("testBleScanUnoptimized", atom, field,
+                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
+
+        BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
+        BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
+        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+    }
+
+    public void testBleUnoptimizedScan() throws Exception {
+        if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
+
+        final int atom = Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER;
+        final int field = BleScanStateChanged.STATE_FIELD_NUMBER;
+        final int stateOn = BleScanStateChanged.State.ON_VALUE;
+        final int stateOff = BleScanStateChanged.State.OFF_VALUE;
+        final int minTimeDiffMillis = 1_500;
+        final int maxTimeDiffMillis = 3_000;
+
+        List<EventMetricData> data = doDeviceMethodOnOff("testBleScanUnoptimized", atom, field,
+                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
+
+        BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
+        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+        assertThat(a0.getIsFiltered()).isFalse();
+        assertThat(a0.getIsFirstMatch()).isFalse();
+        assertThat(a0.getIsOpportunistic()).isFalse();
+        BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
+        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+        assertThat(a1.getIsFiltered()).isFalse();
+        assertThat(a1.getIsFirstMatch()).isFalse();
+        assertThat(a1.getIsOpportunistic()).isFalse();
+
+
+        // Now repeat the test for opportunistic scanning and make sure it is reported correctly.
+        data = doDeviceMethodOnOff("testBleScanOpportunistic", atom, field,
+                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
+
+        a0 = data.get(0).getAtom().getBleScanStateChanged();
+        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+        assertThat(a0.getIsFiltered()).isFalse();
+        assertThat(a0.getIsFirstMatch()).isFalse();
+        assertThat(a0.getIsOpportunistic()).isTrue();  // This scan is opportunistic.
+        a1 = data.get(1).getAtom().getBleScanStateChanged();
+        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+        assertThat(a1.getIsFiltered()).isFalse();
+        assertThat(a1.getIsFirstMatch()).isFalse();
+        assertThat(a1.getIsOpportunistic()).isTrue();
+    }
+
+    public void testBleScanResult() throws Exception {
+        if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
+
+        final int atom = Atom.BLE_SCAN_RESULT_RECEIVED_FIELD_NUMBER;
+        final int field = BleScanResultReceived.NUM_RESULTS_FIELD_NUMBER;
+
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atom, createFvm(field).setGteInt(0));
+        List<EventMetricData> data = doDeviceMethod("testBleScanResult", conf);
+
+        assertThat(data.size()).isAtLeast(1);
+        BleScanResultReceived a0 = data.get(0).getAtom().getBleScanResultReceived();
+        assertThat(a0.getNumResults()).isAtLeast(1);
+    }
+
+    public void testCameraState() throws Exception {
+        if (!hasFeature(FEATURE_CAMERA, true) && !hasFeature(FEATURE_CAMERA_FRONT, true)) return;
+
+        final int atomTag = Atom.CAMERA_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> cameraOn = new HashSet<>(Arrays.asList(CameraStateChanged.State.ON_VALUE));
+        Set<Integer> cameraOff = new HashSet<>(Arrays.asList(CameraStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(cameraOn, cameraOff);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testCameraState");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
+                atom -> atom.getCameraStateChanged().getState().getNumber());
+    }
+
+    public void testDeviceCalculatedPowerUse() throws Exception {
+        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
+
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.DEVICE_CALCULATED_POWER_USE_FIELD_NUMBER, null);
+        uploadConfig(config);
+        unplugDevice();
+
+        Thread.sleep(WAIT_TIME_LONG);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        Atom atom = getGaugeMetricDataList().get(0);
+        assertThat(atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs())
+            .isGreaterThan(0L);
+    }
+
+
+    public void testDeviceCalculatedPowerBlameUid() throws Exception {
+        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
+        if (!hasBattery()) {
+            return;
+        }
+
+        String kernelVersion = getDevice().executeShellCommand("uname -r");
+        if (kernelVersion.contains("3.18")) {
+            LogUtil.CLog.d("Skipping calculated power blame uid test.");
+            return;
+        }
+
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config,
+                Atom.DEVICE_CALCULATED_POWER_BLAME_UID_FIELD_NUMBER, null);
+        uploadConfig(config);
+        unplugDevice();
+
+        Thread.sleep(WAIT_TIME_LONG);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> atomList = getGaugeMetricDataList();
+        boolean uidFound = false;
+        int uid = getUid();
+        long uidPower = 0;
+        for (Atom atom : atomList) {
+            DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
+                if (item.getUid() == uid) {
+                assertWithMessage(String.format("Found multiple power values for uid %d", uid))
+                    .that(uidFound).isFalse();
+                uidFound = true;
+                uidPower = item.getPowerNanoAmpSecs();
+            }
+        }
+        assertWithMessage(String.format("No power value for uid %d", uid)).that(uidFound).isTrue();
+        assertWithMessage(String.format("Non-positive power value for uid %d", uid))
+            .that(uidPower).isGreaterThan(0L);
+    }
+
+    public void testDavey() throws Exception {
+        if (!DAVEY_ENABLED ) return;
+        long MAX_DURATION = 2000;
+        long MIN_DURATION = 750;
+        final int atomTag = Atom.DAVEY_OCCURRED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag, false); // UID is logged without attribution node
+
+        runActivity("DaveyActivity", null, null);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        assertThat(data).hasSize(1);
+        long duration = data.get(0).getAtom().getDaveyOccurred().getJankDurationMillis();
+        assertWithMessage("Incorrect jank duration")
+            .that(duration).isIn(Range.closed(MIN_DURATION, MAX_DURATION));
+    }
+
+    public void testFlashlightState() throws Exception {
+        if (!hasFeature(FEATURE_CAMERA_FLASH, true)) return;
+
+        final int atomTag = Atom.FLASHLIGHT_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testFlashlight";
+
+        Set<Integer> flashlightOn = new HashSet<>(
+            Arrays.asList(FlashlightStateChanged.State.ON_VALUE));
+        Set<Integer> flashlightOff = new HashSet<>(
+            Arrays.asList(FlashlightStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(flashlightOn, flashlightOff);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getFlashlightStateChanged().getState().getNumber());
+    }
+
+    public void testForegroundServiceState() throws Exception {
+        final int atomTag = Atom.FOREGROUND_SERVICE_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testForegroundService";
+
+        Set<Integer> enterForeground = new HashSet<>(
+                Arrays.asList(ForegroundServiceStateChanged.State.ENTER_VALUE));
+        Set<Integer> exitForeground = new HashSet<>(
+                Arrays.asList(ForegroundServiceStateChanged.State.EXIT_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(enterForeground, exitForeground);
+
+        createAndUploadConfig(atomTag, false);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getForegroundServiceStateChanged().getState().getNumber());
+    }
+
+
+    public void testForegroundServiceAccessAppOp() throws Exception {
+        final int atomTag = Atom.FOREGROUND_SERVICE_APP_OP_SESSION_ENDED_FIELD_NUMBER;
+        final String name = "testForegroundServiceAccessAppOp";
+
+        createAndUploadConfig(atomTag, false);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        assertWithMessage("Wrong atom size").that(data.size()).isEqualTo(3);
+        for (int i = 0; i < data.size(); i++) {
+            ForegroundServiceAppOpSessionEnded atom
+                    = data.get(i).getAtom().getForegroundServiceAppOpSessionEnded();
+            final int opName = atom.getAppOpName().getNumber();
+            final int acceptances = atom.getCountOpsAccepted();
+            final int rejections = atom.getCountOpsRejected();
+            final int count = acceptances + rejections;
+            int expectedCount = 0;
+            switch (opName) {
+                case AppOpEnum.APP_OP_CAMERA_VALUE:
+                    expectedCount = 3;
+                    break;
+                case AppOpEnum.APP_OP_FINE_LOCATION_VALUE:
+                    expectedCount = 1;
+                    break;
+                case AppOpEnum.APP_OP_RECORD_AUDIO_VALUE:
+                    expectedCount = 2;
+                    break;
+                case AppOpEnum.APP_OP_COARSE_LOCATION_VALUE:
+                    // fall-through
+                default:
+                    fail("Unexpected opName " + opName);
+            }
+            assertWithMessage("Wrong count for " + opName).that(count).isEqualTo(expectedCount);
+        }
+    }
+
+    public void testGpsScan() throws Exception {
+        if (!hasFeature(FEATURE_LOCATION_GPS, true)) return;
+        // Whitelist this app against background location request throttling
+        String origWhitelist = getDevice().executeShellCommand(
+                "settings get global location_background_throttle_package_whitelist").trim();
+        getDevice().executeShellCommand(String.format(
+                "settings put global location_background_throttle_package_whitelist %s",
+                DEVICE_SIDE_TEST_PACKAGE));
+
+        try {
+            final int atom = Atom.GPS_SCAN_STATE_CHANGED_FIELD_NUMBER;
+            final int key = GpsScanStateChanged.STATE_FIELD_NUMBER;
+            final int stateOn = GpsScanStateChanged.State.ON_VALUE;
+            final int stateOff = GpsScanStateChanged.State.OFF_VALUE;
+            final int minTimeDiffMillis = 500;
+            final int maxTimeDiffMillis = 60_000;
+
+            List<EventMetricData> data = doDeviceMethodOnOff("testGpsScan", atom, key,
+                    stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
+
+            GpsScanStateChanged a0 = data.get(0).getAtom().getGpsScanStateChanged();
+            GpsScanStateChanged a1 = data.get(1).getAtom().getGpsScanStateChanged();
+            assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+            assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+        } finally {
+            if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
+                getDevice().executeShellCommand(
+                        "settings delete global location_background_throttle_package_whitelist");
+            } else {
+                getDevice().executeShellCommand(String.format(
+                        "settings put global location_background_throttle_package_whitelist %s",
+                        origWhitelist));
+            }
+        }
+    }
+
+    public void testGnssStats() throws Exception {
+        // Get GnssMetrics as a simple gauge metric.
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.GNSS_STATS_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        if (!hasFeature(FEATURE_LOCATION_GPS, true)) return;
+        // Whitelist this app against background location request throttling
+        String origWhitelist = getDevice().executeShellCommand(
+                "settings get global location_background_throttle_package_whitelist").trim();
+        getDevice().executeShellCommand(String.format(
+                "settings put global location_background_throttle_package_whitelist %s",
+                DEVICE_SIDE_TEST_PACKAGE));
+
+        try {
+            runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testGpsStatus");
+
+            Thread.sleep(WAIT_TIME_LONG);
+            // Trigger a pull and wait for new pull before killing the process.
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_LONG);
+
+            // Assert about GnssMetrics for the test app.
+            List<Atom> atoms = getGaugeMetricDataList();
+
+            boolean found = false;
+            for (Atom atom : atoms) {
+                AtomsProto.GnssStats state = atom.getGnssStats();
+                found = true;
+                if ((state.getSvStatusReports() > 0 || state.getL5SvStatusReports() > 0)
+                        && state.getLocationReports() == 0) {
+                    // Device is detected to be indoors and not able to acquire location.
+                    // flaky test device
+                    break;
+                }
+                assertThat(state.getLocationReports()).isGreaterThan((long) 0);
+                assertThat(state.getLocationFailureReports()).isAtLeast((long) 0);
+                assertThat(state.getTimeToFirstFixReports()).isGreaterThan((long) 0);
+                assertThat(state.getTimeToFirstFixMillis()).isGreaterThan((long) 0);
+                assertThat(state.getPositionAccuracyReports()).isGreaterThan((long) 0);
+                assertThat(state.getPositionAccuracyMeters()).isGreaterThan((long) 0);
+                assertThat(state.getTopFourAverageCn0Reports()).isGreaterThan((long) 0);
+                assertThat(state.getTopFourAverageCn0DbMhz()).isGreaterThan((long) 0);
+                assertThat(state.getL5TopFourAverageCn0Reports()).isAtLeast((long) 0);
+                assertThat(state.getL5TopFourAverageCn0DbMhz()).isAtLeast((long) 0);
+                assertThat(state.getSvStatusReports()).isAtLeast((long) 0);
+                assertThat(state.getSvStatusReportsUsedInFix()).isAtLeast((long) 0);
+                assertThat(state.getL5SvStatusReports()).isAtLeast((long) 0);
+                assertThat(state.getL5SvStatusReportsUsedInFix()).isAtLeast((long) 0);
+            }
+            assertWithMessage(String.format("Did not find a matching atom"))
+                    .that(found).isTrue();
+        } finally {
+            if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
+                getDevice().executeShellCommand(
+                        "settings delete global location_background_throttle_package_whitelist");
+            } else {
+                getDevice().executeShellCommand(String.format(
+                        "settings put global location_background_throttle_package_whitelist %s",
+                        origWhitelist));
+            }
+        }
+    }
+
+    public void testMediaCodecActivity() throws Exception {
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        final int atomTag = Atom.MEDIA_CODEC_STATE_CHANGED_FIELD_NUMBER;
+
+        // 5 seconds. Starting video tends to be much slower than most other
+        // tests on slow devices. This is unfortunate, because it leaves a
+        // really big slop in assertStatesOccurred.  It would be better if
+        // assertStatesOccurred had a tighter range on large timeouts.
+        final int waitTime = 5000;
+
+        // From {@link VideoPlayerActivity#DELAY_MILLIS}
+        final int videoDuration = 2000;
+
+        Set<Integer> onState = new HashSet<>(
+                Arrays.asList(MediaCodecStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Arrays.asList(MediaCodecStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runActivity("VideoPlayerActivity", "action", "action.play_video",
+            waitTime);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, videoDuration,
+                atom -> atom.getMediaCodecStateChanged().getState().getNumber());
+    }
+
+    public void testOverlayState() throws Exception {
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        final int atomTag = Atom.OVERLAY_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> entered = new HashSet<>(
+                Arrays.asList(OverlayStateChanged.State.ENTERED_VALUE));
+        Set<Integer> exited = new HashSet<>(
+                Arrays.asList(OverlayStateChanged.State.EXITED_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(entered, exited);
+
+        createAndUploadConfig(atomTag, false);
+
+        runActivity("StatsdCtsForegroundActivity", "action", "action.show_application_overlay",
+                5_000);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        // The overlay box should appear about 2sec after the app start
+        assertStatesOccurred(stateSet, data, 0,
+                atom -> atom.getOverlayStateChanged().getState().getNumber());
+    }
+
+    public void testPictureInPictureState() throws Exception {
+        String supported = getDevice().executeShellCommand("am supports-multiwindow");
+        if (!hasFeature(FEATURE_WATCH, false) ||
+                !hasFeature(FEATURE_PICTURE_IN_PICTURE, true) ||
+                !supported.contains("true")) {
+            LogUtil.CLog.d("Skipping picture in picture atom test.");
+            return;
+        }
+
+        StatsdConfig.Builder conf = createConfigBuilder();
+        // PictureInPictureStateChanged atom is used prior to rvc-qpr
+        addAtomEvent(conf, Atom.PICTURE_IN_PICTURE_STATE_CHANGED_FIELD_NUMBER,
+                /*useAttribution=*/false);
+        // Picture-in-picture logs' been migrated to UiEvent since rvc-qpr
+        FieldValueMatcher.Builder pkgMatcher = createFvm(UiEventReported.PACKAGE_NAME_FIELD_NUMBER)
+                .setEqString(DEVICE_SIDE_TEST_PACKAGE);
+        addAtomEvent(conf, Atom.UI_EVENT_REPORTED_FIELD_NUMBER, Arrays.asList(pkgMatcher));
+        uploadConfig(conf);
+
+        LogUtil.CLog.d("Playing video in Picture-in-Picture mode");
+        runActivity("VideoPlayerActivity", "action", "action.play_video_picture_in_picture_mode");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Filter out the PictureInPictureStateChanged and UiEventReported atom
+        List<EventMetricData> pictureInPictureStateChangedData = data.stream()
+                .filter(e -> e.getAtom().hasPictureInPictureStateChanged())
+                .collect(Collectors.toList());
+        List<EventMetricData> uiEventReportedData = data.stream()
+                .filter(e -> e.getAtom().hasUiEventReported())
+                .collect(Collectors.toList());
+
+        assertThat(pictureInPictureStateChangedData).isEmpty();
+        assertThat(uiEventReportedData).isNotEmpty();
+
+        // See PipUiEventEnum for definitions
+        final int enterPipEventId = 603;
+        // Assert that log for entering PiP happens exactly once, we do not use
+        // assertStateOccurred here since PiP may log something else when activity finishes.
+        List<EventMetricData> entered = uiEventReportedData.stream()
+                .filter(e -> e.getAtom().getUiEventReported().getEventId() == enterPipEventId)
+                .collect(Collectors.toList());
+        assertThat(entered).hasSize(1);
+    }
+
+    public void testScheduledJobState() throws Exception {
+        String expectedName = "com.android.server.cts.device.statsd/.StatsdJobService";
+        final int atomTag = Atom.SCHEDULED_JOB_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> jobSchedule = new HashSet<>(
+                Arrays.asList(ScheduledJobStateChanged.State.SCHEDULED_VALUE));
+        Set<Integer> jobOn = new HashSet<>(
+                Arrays.asList(ScheduledJobStateChanged.State.STARTED_VALUE));
+        Set<Integer> jobOff = new HashSet<>(
+                Arrays.asList(ScheduledJobStateChanged.State.FINISHED_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(jobSchedule, jobOn, jobOff);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        allowImmediateSyncs();
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testScheduledJob");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        assertStatesOccurred(stateSet, data, 0,
+                atom -> atom.getScheduledJobStateChanged().getState().getNumber());
+
+        for (EventMetricData e : data) {
+            assertThat(e.getAtom().getScheduledJobStateChanged().getJobName())
+                .isEqualTo(expectedName);
+        }
+    }
+
+    //Note: this test does not have uid, but must run on the device
+    public void testScreenBrightness() throws Exception {
+        int initialBrightness = getScreenBrightness();
+        boolean isInitialManual = isScreenBrightnessModeManual();
+        setScreenBrightnessMode(true);
+        setScreenBrightness(200);
+        Thread.sleep(WAIT_TIME_LONG);
+
+        final int atomTag = Atom.SCREEN_BRIGHTNESS_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> screenMin = new HashSet<>(Arrays.asList(47));
+        Set<Integer> screen100 = new HashSet<>(Arrays.asList(100));
+        Set<Integer> screen200 = new HashSet<>(Arrays.asList(198));
+        // Set<Integer> screenMax = new HashSet<>(Arrays.asList(255));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(screenMin, screen100, screen200);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testScreenBrightness");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Restore initial screen brightness
+        setScreenBrightness(initialBrightness);
+        setScreenBrightnessMode(isInitialManual);
+
+        popUntilFind(data, screenMin, atom->atom.getScreenBrightnessChanged().getLevel());
+        popUntilFindFromEnd(data, screen200, atom->atom.getScreenBrightnessChanged().getLevel());
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+            atom -> atom.getScreenBrightnessChanged().getLevel());
+    }
+    public void testSyncState() throws Exception {
+        final int atomTag = Atom.SYNC_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> syncOn = new HashSet<>(Arrays.asList(SyncStateChanged.State.ON_VALUE));
+        Set<Integer> syncOff = new HashSet<>(Arrays.asList(SyncStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(syncOn, syncOff, syncOn, syncOff);
+
+        createAndUploadConfig(atomTag, true);
+        allowImmediateSyncs();
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSyncState");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data,
+            /* wait = */ 0 /* don't verify time differences between state changes */,
+            atom -> atom.getSyncStateChanged().getState().getNumber());
+    }
+
+    public void testVibratorState() throws Exception {
+        if (!checkDeviceFor("checkVibratorSupported")) return;
+
+        final int atomTag = Atom.VIBRATOR_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testVibratorState";
+
+        Set<Integer> onState = new HashSet<>(
+                Arrays.asList(VibratorStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Arrays.asList(VibratorStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        assertStatesOccurred(stateSet, data, 300,
+                atom -> atom.getVibratorStateChanged().getState().getNumber());
+    }
+
+    public void testWakelockState() throws Exception {
+        final int atomTag = Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> wakelockOn = new HashSet<>(Arrays.asList(
+                WakelockStateChanged.State.ACQUIRE_VALUE,
+                WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE));
+        Set<Integer> wakelockOff = new HashSet<>(Arrays.asList(
+                WakelockStateChanged.State.RELEASE_VALUE,
+                WakelockStateChanged.State.CHANGE_RELEASE_VALUE));
+
+        final String EXPECTED_TAG = "StatsdPartialWakelock";
+        final WakeLockLevelEnum EXPECTED_LEVEL = WakeLockLevelEnum.PARTIAL_WAKE_LOCK;
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(wakelockOn, wakelockOff);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockState");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+            atom -> atom.getWakelockStateChanged().getState().getNumber());
+
+        for (EventMetricData event: data) {
+            String tag = event.getAtom().getWakelockStateChanged().getTag();
+            WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getType();
+            assertThat(tag).isEqualTo(EXPECTED_TAG);
+            assertThat(type).isEqualTo(EXPECTED_LEVEL);
+        }
+    }
+
+    public void testWakeupAlarm() throws Exception {
+        // For automotive, all wakeup alarm becomes normal alarm. So this
+        // test does not work.
+        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
+        final int atomTag = Atom.WAKEUP_ALARM_OCCURRED_FIELD_NUMBER;
+
+        StatsdConfig.Builder config = createConfigBuilder();
+        addAtomEvent(config, atomTag, true);  // True: uses attribution.
+
+        List<EventMetricData> data = doDeviceMethod("testWakeupAlarm", config);
+        assertThat(data.size()).isAtLeast(1);
+        for (int i = 0; i < data.size(); i++) {
+            WakeupAlarmOccurred wao = data.get(i).getAtom().getWakeupAlarmOccurred();
+            assertThat(wao.getTag()).isEqualTo("*walarm*:android.cts.statsd.testWakeupAlarm");
+            assertThat(wao.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+        }
+    }
+
+    public void testWifiLockHighPerf() throws Exception {
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+        if (!hasFeature(FEATURE_PC, false)) return;
+
+        final int atomTag = Atom.WIFI_LOCK_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> lockOn = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.ON_VALUE));
+        Set<Integer> lockOff = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWifiLockHighPerf");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getWifiLockStateChanged().getState().getNumber());
+
+        for (EventMetricData event : data) {
+            assertThat(event.getAtom().getWifiLockStateChanged().getMode())
+                .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_HIGH_PERF);
+        }
+    }
+
+    public void testWifiLockLowLatency() throws Exception {
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+        if (!hasFeature(FEATURE_PC, false)) return;
+
+        final int atomTag = Atom.WIFI_LOCK_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> lockOn = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.ON_VALUE));
+        Set<Integer> lockOff = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWifiLockLowLatency");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getWifiLockStateChanged().getState().getNumber());
+
+        for (EventMetricData event : data) {
+            assertThat(event.getAtom().getWifiLockStateChanged().getMode())
+                .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_LOW_LATENCY);
+        }
+    }
+
+    public void testWifiMulticastLock() throws Exception {
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+        if (!hasFeature(FEATURE_PC, false)) return;
+
+        final int atomTag = Atom.WIFI_MULTICAST_LOCK_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> lockOn = new HashSet<>(
+                Arrays.asList(WifiMulticastLockStateChanged.State.ON_VALUE));
+        Set<Integer> lockOff = new HashSet<>(
+                Arrays.asList(WifiMulticastLockStateChanged.State.OFF_VALUE));
+
+        final String EXPECTED_TAG = "StatsdCTSMulticastLock";
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
+
+        createAndUploadConfig(atomTag, true);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWifiMulticastLock");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getWifiMulticastLockStateChanged().getState().getNumber());
+
+        for (EventMetricData event: data) {
+            String tag = event.getAtom().getWifiMulticastLockStateChanged().getTag();
+            assertThat(tag).isEqualTo(EXPECTED_TAG);
+        }
+    }
+
+    public void testWifiScan() throws Exception {
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+
+        final int atom = Atom.WIFI_SCAN_STATE_CHANGED_FIELD_NUMBER;
+        final int key = WifiScanStateChanged.STATE_FIELD_NUMBER;
+        final int stateOn = WifiScanStateChanged.State.ON_VALUE;
+        final int stateOff = WifiScanStateChanged.State.OFF_VALUE;
+        final int minTimeDiffMillis = 250;
+        final int maxTimeDiffMillis = 60_000;
+        final boolean demandExactlyTwo = false; // Two scans are performed, so up to 4 atoms logged.
+
+        List<EventMetricData> data = doDeviceMethodOnOff("testWifiScan", atom, key,
+                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, demandExactlyTwo);
+
+        assertThat(data.size()).isIn(Range.closed(2, 4));
+        WifiScanStateChanged a0 = data.get(0).getAtom().getWifiScanStateChanged();
+        WifiScanStateChanged a1 = data.get(1).getAtom().getWifiScanStateChanged();
+        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+    }
+
+    public void testBinderStats() throws Exception {
+        try {
+            unplugDevice();
+            Thread.sleep(WAIT_TIME_SHORT);
+            enableBinderStats();
+            binderStatsNoSampling();
+            resetBinderStats();
+            StatsdConfig.Builder config = createConfigBuilder();
+            addGaugeAtomWithDimensions(config, Atom.BINDER_CALLS_FIELD_NUMBER, null);
+
+            uploadConfig(config);
+            Thread.sleep(WAIT_TIME_SHORT);
+
+            runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification",3_000);
+
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_SHORT);
+
+            boolean found = false;
+            int uid = getUid();
+            List<Atom> atomList = getGaugeMetricDataList();
+            for (Atom atom : atomList) {
+                BinderCalls calls = atom.getBinderCalls();
+                boolean classMatches = calls.getServiceClassName().contains(
+                        "com.android.server.notification.NotificationManagerService");
+                boolean methodMatches = calls.getServiceMethodName()
+                        .equals("createNotificationChannels");
+
+                if (calls.getUid() == uid && classMatches && methodMatches) {
+                    found = true;
+                    assertThat(calls.getRecordedCallCount()).isGreaterThan(0L);
+                    assertThat(calls.getCallCount()).isGreaterThan(0L);
+                    assertThat(calls.getRecordedTotalLatencyMicros())
+                        .isIn(Range.open(0L, 1000000L));
+                    assertThat(calls.getRecordedTotalCpuMicros()).isIn(Range.open(0L, 1000000L));
+                }
+            }
+
+            assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
+                .that(found).isTrue();
+
+        } finally {
+            disableBinderStats();
+            plugInAc();
+        }
+    }
+
+    public void testLooperStats() throws Exception {
+        try {
+            unplugDevice();
+            setUpLooperStats();
+            StatsdConfig.Builder config = createConfigBuilder();
+            addGaugeAtomWithDimensions(config, Atom.LOOPER_STATS_FIELD_NUMBER, null);
+            uploadConfig(config);
+            Thread.sleep(WAIT_TIME_SHORT);
+
+            runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification", 3_000);
+
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_SHORT);
+
+            List<Atom> atomList = getGaugeMetricDataList();
+
+            boolean found = false;
+            int uid = getUid();
+            for (Atom atom : atomList) {
+                LooperStats stats = atom.getLooperStats();
+                String notificationServiceFullName =
+                        "com.android.server.notification.NotificationManagerService";
+                boolean handlerMatches =
+                        stats.getHandlerClassName().equals(
+                                notificationServiceFullName + "$WorkerHandler");
+                boolean messageMatches =
+                        stats.getMessageName().equals(
+                                notificationServiceFullName + "$EnqueueNotificationRunnable");
+                if (atom.getLooperStats().getUid() == uid && handlerMatches && messageMatches) {
+                    found = true;
+                    assertThat(stats.getMessageCount()).isGreaterThan(0L);
+                    assertThat(stats.getRecordedMessageCount()).isGreaterThan(0L);
+                    assertThat(stats.getRecordedTotalLatencyMicros())
+                        .isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedTotalCpuMicros()).isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedMaxLatencyMicros()).isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedMaxCpuMicros()).isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedDelayMessageCount()).isGreaterThan(0L);
+                    assertThat(stats.getRecordedTotalDelayMillis())
+                        .isIn(Range.closedOpen(0L, 5000L));
+                    assertThat(stats.getRecordedMaxDelayMillis()).isIn(Range.closedOpen(0L, 5000L));
+                }
+            }
+            assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
+                .that(found).isTrue();
+        } finally {
+            cleanUpLooperStats();
+            plugInAc();
+        }
+    }
+
+    public void testProcessMemoryState() throws Exception {
+        // Get ProcessMemoryState as a simple gauge metric.
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Start test app.
+        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            Thread.sleep(WAIT_TIME_LONG);
+            // Trigger a pull and wait for new pull before killing the process.
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_LONG);
+        }
+
+        // Assert about ProcessMemoryState for the test app.
+        List<Atom> atoms = getGaugeMetricDataList();
+        int uid = getUid();
+        boolean found = false;
+        for (Atom atom : atoms) {
+            ProcessMemoryState state = atom.getProcessMemoryState();
+            if (state.getUid() != uid) {
+                continue;
+            }
+            found = true;
+            assertThat(state.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+            assertThat(state.getOomAdjScore()).isAtLeast(0);
+            assertThat(state.getPageFault()).isAtLeast(0L);
+            assertThat(state.getPageMajorFault()).isAtLeast(0L);
+            assertThat(state.getRssInBytes()).isGreaterThan(0L);
+            assertThat(state.getCacheInBytes()).isAtLeast(0L);
+            assertThat(state.getSwapInBytes()).isAtLeast(0L);
+        }
+        assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
+            .that(found).isTrue();
+    }
+
+    public void testProcessMemoryHighWaterMark() throws Exception {
+        // Get ProcessMemoryHighWaterMark as a simple gauge metric.
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_HIGH_WATER_MARK_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Start test app and trigger a pull while it is running.
+        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            Thread.sleep(WAIT_TIME_SHORT);
+            // Trigger a pull and wait for new pull before killing the process.
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_LONG);
+        }
+
+        // Assert about ProcessMemoryHighWaterMark for the test app, statsd and system server.
+        List<Atom> atoms = getGaugeMetricDataList();
+        int uid = getUid();
+        boolean foundTestApp = false;
+        boolean foundStatsd = false;
+        boolean foundSystemServer = false;
+        for (Atom atom : atoms) {
+            ProcessMemoryHighWaterMark state = atom.getProcessMemoryHighWaterMark();
+            if (state.getUid() == uid) {
+                foundTestApp = true;
+                assertThat(state.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
+            } else if (state.getProcessName().contains("/statsd")) {
+                foundStatsd = true;
+                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
+            } else if (state.getProcessName().equals("system")) {
+                foundSystemServer = true;
+                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
+            }
+        }
+        assertWithMessage(String.format("Did not find a matching atom for test app uid=%d",uid))
+            .that(foundTestApp).isTrue();
+        assertWithMessage("Did not find a matching atom for statsd").that(foundStatsd).isTrue();
+        assertWithMessage("Did not find a matching atom for system server")
+            .that(foundSystemServer).isTrue();
+    }
+
+    public void testProcessMemorySnapshot() throws Exception {
+        // Get ProcessMemorySnapshot as a simple gauge metric.
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Start test app and trigger a pull while it is running.
+        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            Thread.sleep(WAIT_TIME_LONG);
+            setAppBreadcrumbPredicate();
+        }
+
+        // Assert about ProcessMemorySnapshot for the test app, statsd and system server.
+        List<Atom> atoms = getGaugeMetricDataList();
+        int uid = getUid();
+        boolean foundTestApp = false;
+        boolean foundStatsd = false;
+        boolean foundSystemServer = false;
+        for (Atom atom : atoms) {
+          ProcessMemorySnapshot snapshot = atom.getProcessMemorySnapshot();
+          if (snapshot.getUid() == uid) {
+              foundTestApp = true;
+              assertThat(snapshot.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+          } else if (snapshot.getProcessName().contains("/statsd")) {
+              foundStatsd = true;
+          } else if (snapshot.getProcessName().equals("system")) {
+              foundSystemServer = true;
+          }
+
+          assertThat(snapshot.getPid()).isGreaterThan(0);
+          assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isAtLeast(0);
+          assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isEqualTo(
+                  snapshot.getAnonRssInKilobytes() + snapshot.getSwapInKilobytes());
+          assertThat(snapshot.getRssInKilobytes()).isAtLeast(0);
+          assertThat(snapshot.getAnonRssInKilobytes()).isAtLeast(0);
+          assertThat(snapshot.getSwapInKilobytes()).isAtLeast(0);
+        }
+        assertWithMessage(String.format("Did not find a matching atom for test app uid=%d",uid))
+            .that(foundTestApp).isTrue();
+        assertWithMessage("Did not find a matching atom for statsd").that(foundStatsd).isTrue();
+        assertWithMessage("Did not find a matching atom for system server")
+            .that(foundSystemServer).isTrue();
+    }
+
+    public void testIonHeapSize_optional() throws Exception {
+        if (isIonHeapSizeMandatory()) {
+            return;
+        }
+
+        List<Atom> atoms = pullIonHeapSizeAsGaugeMetric();
+        if (atoms.isEmpty()) {
+            // No support.
+            return;
+        }
+        assertIonHeapSize(atoms);
+    }
+
+    public void testIonHeapSize_mandatory() throws Exception {
+        if (!isIonHeapSizeMandatory()) {
+            return;
+        }
+
+        List<Atom> atoms = pullIonHeapSizeAsGaugeMetric();
+        assertIonHeapSize(atoms);
+    }
+
+    /** Returns whether IonHeapSize atom is supported. */
+    private boolean isIonHeapSizeMandatory() throws Exception {
+        // Support is guaranteed by libmeminfo VTS.
+        return PropertyUtil.getFirstApiLevel(getDevice()) >= 30;
+    }
+
+    /** Returns IonHeapSize atoms pulled as a simple gauge metric while test app is running. */
+    private List<Atom> pullIonHeapSizeAsGaugeMetric() throws Exception {
+        // Get IonHeapSize as a simple gauge metric.
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.ION_HEAP_SIZE_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Start test app and trigger a pull while it is running.
+        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_LONG);
+        }
+
+        return getGaugeMetricDataList();
+    }
+
+    private static void assertIonHeapSize(List<Atom> atoms) {
+        assertThat(atoms).hasSize(1);
+        IonHeapSize ionHeapSize = atoms.get(0).getIonHeapSize();
+        assertThat(ionHeapSize.getTotalSizeKb()).isAtLeast(0);
+    }
+
+    /**
+     * The the app id from a uid.
+     *
+     * @param uid The uid of the app
+     *
+     * @return The app id of the app
+     *
+     * @see android.os.UserHandle#getAppId
+     */
+    private static int getAppId(int uid) {
+        return uid % 100000;
+    }
+
+    public void testRoleHolder() throws Exception {
+        // Make device side test package a role holder
+        String callScreenAppRole = "android.app.role.CALL_SCREENING";
+        getDevice().executeShellCommand(
+                "cmd role add-role-holder " + callScreenAppRole + " " + DEVICE_SIDE_TEST_PACKAGE);
+
+        // Set up what to collect
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.ROLE_HOLDER_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        boolean verifiedKnowRoleState = false;
+
+        // Pull a report
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        int testAppId = getAppId(getUid());
+
+        for (Atom atom : getGaugeMetricDataList()) {
+            AtomsProto.RoleHolder roleHolder = atom.getRoleHolder();
+
+            assertThat(roleHolder.getPackageName()).isNotNull();
+            assertThat(roleHolder.getUid()).isAtLeast(0);
+            assertThat(roleHolder.getRole()).isNotNull();
+
+            if (roleHolder.getPackageName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
+                assertThat(getAppId(roleHolder.getUid())).isEqualTo(testAppId);
+                assertThat(roleHolder.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+                assertThat(roleHolder.getRole()).isEqualTo(callScreenAppRole);
+
+                verifiedKnowRoleState = true;
+            }
+        }
+
+        assertThat(verifiedKnowRoleState).isTrue();
+    }
+
+    public void testDangerousPermissionState() throws Exception {
+        final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED =  1 << 8;
+        final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED =  1 << 9;
+
+        // Set up what to collect
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        boolean verifiedKnowPermissionState = false;
+
+        // Pull a report
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        int testAppId = getAppId(getUid());
+
+        for (Atom atom : getGaugeMetricDataList()) {
+            DangerousPermissionState permissionState = atom.getDangerousPermissionState();
+
+            assertThat(permissionState.getPermissionName()).isNotNull();
+            assertThat(permissionState.getUid()).isAtLeast(0);
+            assertThat(permissionState.getPackageName()).isNotNull();
+
+            if (getAppId(permissionState.getUid()) == testAppId) {
+
+                if (permissionState.getPermissionName().contains(
+                        "ACCESS_FINE_LOCATION")) {
+                    assertThat(permissionState.getIsGranted()).isTrue();
+                    assertThat(permissionState.getPermissionFlags() & ~(
+                            FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
+                            | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED))
+                        .isEqualTo(0);
+
+                    verifiedKnowPermissionState = true;
+                }
+            }
+        }
+
+        assertThat(verifiedKnowPermissionState).isTrue();
+    }
+
+    public void testDangerousPermissionStateSampled() throws Exception {
+        // get full atom for reference
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<DangerousPermissionState> fullDangerousPermissionState = new ArrayList<>();
+        for (Atom atom : getGaugeMetricDataList()) {
+            fullDangerousPermissionState.add(atom.getDangerousPermissionState());
+        }
+
+        removeConfig(CONFIG_ID);
+        getReportList(); // Clears data.
+        List<Atom> gaugeMetricDataList = null;
+
+        // retries in case sampling returns full list or empty list - which should be extremely rare
+        for (int attempt = 0; attempt < 10; attempt++) {
+            // Set up what to collect
+            config = createConfigBuilder();
+            addGaugeAtomWithDimensions(config, Atom.DANGEROUS_PERMISSION_STATE_SAMPLED_FIELD_NUMBER,
+                    null);
+            uploadConfig(config);
+            Thread.sleep(WAIT_TIME_SHORT);
+
+            // Pull a report
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_SHORT);
+
+            gaugeMetricDataList = getGaugeMetricDataList();
+            if (gaugeMetricDataList.size() > 0
+                    && gaugeMetricDataList.size() < fullDangerousPermissionState.size()) {
+                break;
+            }
+            removeConfig(CONFIG_ID);
+            getReportList(); // Clears data.
+        }
+        assertThat(gaugeMetricDataList.size()).isGreaterThan(0);
+        assertThat(gaugeMetricDataList.size()).isLessThan(fullDangerousPermissionState.size());
+
+        long lastUid = -1;
+        int fullIndex = 0;
+
+        for (Atom atom : getGaugeMetricDataList()) {
+            DangerousPermissionStateSampled permissionState =
+                    atom.getDangerousPermissionStateSampled();
+
+            DangerousPermissionState referenceState = fullDangerousPermissionState.get(fullIndex);
+
+            if (referenceState.getUid() != permissionState.getUid()) {
+                // atoms are sampled on uid basis if uid is present, all related permissions must
+                // be logged.
+                assertThat(permissionState.getUid()).isNotEqualTo(lastUid);
+                continue;
+            }
+
+            lastUid = permissionState.getUid();
+
+            assertThat(permissionState.getPermissionFlags()).isEqualTo(
+                    referenceState.getPermissionFlags());
+            assertThat(permissionState.getIsGranted()).isEqualTo(referenceState.getIsGranted());
+            assertThat(permissionState.getPermissionName()).isEqualTo(
+                    referenceState.getPermissionName());
+
+            fullIndex++;
+        }
+    }
+
+    public void testAppOps() throws Exception {
+        // Set up what to collect
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.APP_OPS_FIELD_NUMBER, null);
+        uploadConfig(config);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testAppOps");
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Pull a report
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        ArrayList<Integer> expectedOps = new ArrayList<>();
+        for (int i = 0; i < NUM_APP_OPS; i++) {
+            expectedOps.add(i);
+        }
+
+        for (Descriptors.EnumValueDescriptor valueDescriptor :
+                AttributedAppOps.getDefaultInstance().getOp().getDescriptorForType().getValues()) {
+            if (valueDescriptor.getOptions().hasDeprecated()) {
+                // Deprecated app op, remove from list of expected ones.
+                expectedOps.remove(expectedOps.indexOf(valueDescriptor.getNumber()));
+            }
+        }
+        for (Atom atom : getGaugeMetricDataList()) {
+
+            AppOps appOps = atom.getAppOps();
+            if (appOps.getPackageName().equals(TEST_PACKAGE_NAME)) {
+                if (appOps.getOpId().getNumber() == -1) {
+                    continue;
+                }
+                long totalNoted = appOps.getTrustedForegroundGrantedCount()
+                        + appOps.getTrustedBackgroundGrantedCount()
+                        + appOps.getTrustedForegroundRejectedCount()
+                        + appOps.getTrustedBackgroundRejectedCount();
+                assertWithMessage("Operation in APP_OPS_ENUM_MAP: " + appOps.getOpId().getNumber())
+                        .that(totalNoted - 1).isEqualTo(appOps.getOpId().getNumber());
+                assertWithMessage("Unexpected Op reported").that(expectedOps).contains(
+                        appOps.getOpId().getNumber());
+                expectedOps.remove(expectedOps.indexOf(appOps.getOpId().getNumber()));
+            }
+        }
+        assertWithMessage("Logging app op ids are missing in report.").that(expectedOps).isEmpty();
+    }
+
+    public void testANROccurred() throws Exception {
+        final int atomTag = Atom.ANR_OCCURRED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag, false);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        try (AutoCloseable a = withActivity("ANRActivity", null, null)) {
+            Thread.sleep(WAIT_TIME_SHORT);
+            getDevice().executeShellCommand(
+                    "am broadcast -a action_anr -p " + DEVICE_SIDE_TEST_PACKAGE);
+            Thread.sleep(20_000);
+        }
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        assertThat(data).hasSize(1);
+        assertThat(data.get(0).getAtom().hasAnrOccurred()).isTrue();
+        ANROccurred atom = data.get(0).getAtom().getAnrOccurred();
+        assertThat(atom.getIsInstantApp().getNumber())
+            .isEqualTo(ANROccurred.InstantApp.FALSE_VALUE);
+        assertThat(atom.getForegroundState().getNumber())
+            .isEqualTo(ANROccurred.ForegroundState.FOREGROUND_VALUE);
+        assertThat(atom.getErrorSource()).isEqualTo(ErrorSource.DATA_APP);
+        assertThat(atom.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+    }
+
+    public void testWriteRawTestAtom() throws Exception {
+        final int atomTag = Atom.TEST_ATOM_REPORTED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag, true);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWriteRawTestAtom");
+
+        Thread.sleep(WAIT_TIME_SHORT);
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+        assertThat(data).hasSize(4);
+
+        TestAtomReported atom = data.get(0).getAtom().getTestAtomReported();
+        List<AttributionNode> attrChain = atom.getAttributionNodeList();
+        assertThat(attrChain).hasSize(2);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(1234);
+        assertThat(attrChain.get(0).getTag()).isEqualTo("tag1");
+        assertThat(attrChain.get(1).getUid()).isEqualTo(getUid());
+        assertThat(attrChain.get(1).getTag()).isEqualTo("tag2");
+
+        assertThat(atom.getIntField()).isEqualTo(42);
+        assertThat(atom.getLongField()).isEqualTo(Long.MAX_VALUE);
+        assertThat(atom.getFloatField()).isEqualTo(3.14f);
+        assertThat(atom.getStringField()).isEqualTo("This is a basic test!");
+        assertThat(atom.getBooleanField()).isFalse();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.ON_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList())
+            .containsExactly(1L, 2L, 3L).inOrder();
+
+
+        atom = data.get(1).getAtom().getTestAtomReported();
+        attrChain = atom.getAttributionNodeList();
+        assertThat(attrChain).hasSize(2);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(9999);
+        assertThat(attrChain.get(0).getTag()).isEqualTo("tag9999");
+        assertThat(attrChain.get(1).getUid()).isEqualTo(getUid());
+        assertThat(attrChain.get(1).getTag()).isEmpty();
+
+        assertThat(atom.getIntField()).isEqualTo(100);
+        assertThat(atom.getLongField()).isEqualTo(Long.MIN_VALUE);
+        assertThat(atom.getFloatField()).isEqualTo(-2.5f);
+        assertThat(atom.getStringField()).isEqualTo("Test null uid");
+        assertThat(atom.getBooleanField()).isTrue();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.UNKNOWN_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList())
+            .containsExactly(1L, 2L, 3L).inOrder();
+
+        atom = data.get(2).getAtom().getTestAtomReported();
+        attrChain = atom.getAttributionNodeList();
+        assertThat(attrChain).hasSize(1);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(getUid());
+        assertThat(attrChain.get(0).getTag()).isEqualTo("tag1");
+
+        assertThat(atom.getIntField()).isEqualTo(-256);
+        assertThat(atom.getLongField()).isEqualTo(-1234567890L);
+        assertThat(atom.getFloatField()).isEqualTo(42.01f);
+        assertThat(atom.getStringField()).isEqualTo("Test non chained");
+        assertThat(atom.getBooleanField()).isTrue();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList())
+            .containsExactly(1L, 2L, 3L).inOrder();
+
+        atom = data.get(3).getAtom().getTestAtomReported();
+        attrChain = atom.getAttributionNodeList();
+        assertThat(attrChain).hasSize(1);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(getUid());
+        assertThat(attrChain.get(0).getTag()).isEmpty();
+
+        assertThat(atom.getIntField()).isEqualTo(0);
+        assertThat(atom.getLongField()).isEqualTo(0L);
+        assertThat(atom.getFloatField()).isEqualTo(0f);
+        assertThat(atom.getStringField()).isEmpty();
+        assertThat(atom.getBooleanField()).isTrue();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList()).isEmpty();
+    }
+
+    public void testNotificationPackagePreferenceExtraction() throws Exception {
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config,
+                    Atom.PACKAGE_NOTIFICATION_PREFERENCES_FIELD_NUMBER,
+                    null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+        runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification");
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<PackageNotificationPreferences> allPreferences = new ArrayList<>();
+        for (Atom atom : getGaugeMetricDataList()){
+            if(atom.hasPackageNotificationPreferences()) {
+                allPreferences.add(atom.getPackageNotificationPreferences());
+            }
+        }
+        assertThat(allPreferences.size()).isGreaterThan(0);
+
+        boolean foundTestPackagePreferences = false;
+        int uid = getUid();
+        for (PackageNotificationPreferences pref : allPreferences) {
+            assertThat(pref.getUid()).isGreaterThan(0);
+            assertTrue(pref.hasImportance());
+            assertTrue(pref.hasVisibility());
+            assertTrue(pref.hasUserLockedFields());
+            if(pref.getUid() == uid){
+                assertThat(pref.getImportance()).isEqualTo(-1000);  //UNSPECIFIED_IMPORTANCE
+                assertThat(pref.getVisibility()).isEqualTo(-1000);  //UNSPECIFIED_VISIBILITY
+                foundTestPackagePreferences = true;
+            }
+        }
+        assertTrue(foundTestPackagePreferences);
+    }
+
+    public void testNotificationChannelPreferencesExtraction() throws Exception {
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config,
+                    Atom.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES_FIELD_NUMBER,
+                    null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+        runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification");
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<PackageNotificationChannelPreferences> allChannelPreferences = new ArrayList<>();
+        for(Atom atom : getGaugeMetricDataList()) {
+            if (atom.hasPackageNotificationChannelPreferences()) {
+               allChannelPreferences.add(atom.getPackageNotificationChannelPreferences());
+            }
+        }
+        assertThat(allChannelPreferences.size()).isGreaterThan(0);
+
+        boolean foundTestPackagePreferences = false;
+        int uid = getUid();
+        for (PackageNotificationChannelPreferences pref : allChannelPreferences) {
+            assertThat(pref.getUid()).isGreaterThan(0);
+            assertTrue(pref.hasChannelId());
+            assertTrue(pref.hasChannelName());
+            assertTrue(pref.hasDescription());
+            assertTrue(pref.hasImportance());
+            assertTrue(pref.hasUserLockedFields());
+            assertTrue(pref.hasIsDeleted());
+            if(uid == pref.getUid() && pref.getChannelId().equals("StatsdCtsChannel")) {
+                assertThat(pref.getChannelName()).isEqualTo("Statsd Cts");
+                assertThat(pref.getDescription()).isEqualTo("Statsd Cts Channel");
+                assertThat(pref.getImportance()).isEqualTo(3);  // IMPORTANCE_DEFAULT
+                foundTestPackagePreferences = true;
+            }
+        }
+        assertTrue(foundTestPackagePreferences);
+    }
+
+    public void testNotificationChannelGroupPreferencesExtraction() throws Exception {
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config,
+                    Atom.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES_FIELD_NUMBER,
+                    null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+        runActivity("StatsdCtsForegroundActivity", "action", "action.create_channel_group");
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<PackageNotificationChannelGroupPreferences> allGroupPreferences = new ArrayList<>();
+        for(Atom atom : getGaugeMetricDataList()) {
+            if (atom.hasPackageNotificationChannelGroupPreferences()) {
+                allGroupPreferences.add(atom.getPackageNotificationChannelGroupPreferences());
+            }
+        }
+        assertThat(allGroupPreferences.size()).isGreaterThan(0);
+
+        boolean foundTestPackagePreferences = false;
+        int uid = getUid();
+        for(PackageNotificationChannelGroupPreferences pref : allGroupPreferences) {
+            assertThat(pref.getUid()).isGreaterThan(0);
+            assertTrue(pref.hasGroupId());
+            assertTrue(pref.hasGroupName());
+            assertTrue(pref.hasDescription());
+            assertTrue(pref.hasIsBlocked());
+            assertTrue(pref.hasUserLockedFields());
+            if(uid == pref.getUid() && pref.getGroupId().equals("StatsdCtsGroup")) {
+                assertThat(pref.getGroupName()).isEqualTo("Statsd Cts Group");
+                assertThat(pref.getDescription()).isEqualTo("StatsdCtsGroup Description");
+                assertThat(pref.getIsBlocked()).isFalse();
+                foundTestPackagePreferences = true;
+            }
+        }
+        assertTrue(foundTestPackagePreferences);
+    }
+
+    public void testNotificationReported() throws Exception {
+        StatsdConfig.Builder config = getPulledConfig();
+        addAtomEvent(config, Atom.NOTIFICATION_REPORTED_FIELD_NUMBER,
+            Arrays.asList(createFvm(NotificationReported.PACKAGE_NAME_FIELD_NUMBER)
+                              .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+        runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification");
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+        assertThat(data).hasSize(1);
+        assertThat(data.get(0).getAtom().hasNotificationReported()).isTrue();
+        AtomsProto.NotificationReported n = data.get(0).getAtom().getNotificationReported();
+        assertThat(n.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+        assertThat(n.getUid()).isEqualTo(getUid());
+        assertThat(n.getNotificationIdHash()).isEqualTo(1);  // smallHash(0x7f080001)
+        assertThat(n.getChannelIdHash()).isEqualTo(SmallHash.hash("StatsdCtsChannel"));
+        assertThat(n.getGroupIdHash()).isEqualTo(0);
+        assertFalse(n.getIsGroupSummary());
+        assertThat(n.getCategory()).isEmpty();
+        assertThat(n.getStyle()).isEqualTo(0);
+        assertThat(n.getNumPeople()).isEqualTo(0);
+    }
+
+    public void testSettingsStatsReported() throws Exception {
+        // Base64 encoded proto com.android.service.nano.StringListParamProto,
+        // which contains two strings "font_scale" and "screen_auto_brightness_adj".
+        final String encoded = "ChpzY3JlZW5fYXV0b19icmlnaHRuZXNzX2FkagoKZm9udF9zY2FsZQ";
+        final String font_scale = "font_scale";
+        SettingSnapshot snapshot = null;
+
+        // Set whitelist through device config.
+        Thread.sleep(WAIT_TIME_SHORT);
+        getDevice().executeShellCommand(
+                "device_config put settings_stats SystemFeature__float_whitelist " + encoded);
+        Thread.sleep(WAIT_TIME_SHORT);
+        // Set font_scale value
+        getDevice().executeShellCommand("settings put system font_scale 1.5");
+
+        // Get SettingSnapshot as a simple gauge metric.
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.SETTING_SNAPSHOT_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Start test app and trigger a pull while it is running.
+        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            Thread.sleep(WAIT_TIME_SHORT);
+            // Trigger a pull and wait for new pull before killing the process.
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_LONG);
+        }
+
+        // Test the size of atoms. It should contain at least "font_scale" and
+        // "screen_auto_brightness_adj" two setting values.
+        List<Atom> atoms = getGaugeMetricDataList();
+        assertThat(atoms.size()).isAtLeast(2);
+        for (Atom atom : atoms) {
+            SettingSnapshot settingSnapshot = atom.getSettingSnapshot();
+            if (font_scale.equals(settingSnapshot.getName())) {
+                snapshot = settingSnapshot;
+                break;
+            }
+        }
+
+        Thread.sleep(WAIT_TIME_SHORT);
+        // Test the data of atom.
+        assertNotNull(snapshot);
+        // Get font_scale value and test value type.
+        final float fontScale = Float.parseFloat(
+                getDevice().executeShellCommand("settings get system font_scale"));
+        assertThat(snapshot.getType()).isEqualTo(
+                SettingSnapshot.SettingsValueType.ASSIGNED_FLOAT_TYPE);
+        assertThat(snapshot.getBoolValue()).isEqualTo(false);
+        assertThat(snapshot.getIntValue()).isEqualTo(0);
+        assertThat(snapshot.getFloatValue()).isEqualTo(fontScale);
+        assertThat(snapshot.getStrValue()).isEqualTo("");
+        assertThat(snapshot.getUserId()).isEqualTo(0);
+    }
+
+    public void testIntegrityCheckAtomReportedDuringInstall() throws Exception {
+        createAndUploadConfig(AtomsProto.Atom.INTEGRITY_CHECK_RESULT_REPORTED_FIELD_NUMBER);
+
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        installTestApp();
+
+        List<EventMetricData> data = getEventMetricDataList();
+
+        assertThat(data.size()).isEqualTo(1);
+        assertThat(data.get(0).getAtom().hasIntegrityCheckResultReported()).isTrue();
+        IntegrityCheckResultReported result = data.get(0)
+                .getAtom().getIntegrityCheckResultReported();
+        assertThat(result.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+        // we do not assert on certificates since it seem to differ by device.
+        assertThat(result.getInstallerPackageName()).isEqualTo("adb");
+        assertThat(result.getVersionCode()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE_VERSION);
+        assertThat(result.getResponse()).isEqualTo(ALLOWED);
+        assertThat(result.getCausedByAppCertRule()).isFalse();
+        assertThat(result.getCausedByInstallerRule()).isFalse();
+    }
+
+    public void testMobileBytesTransfer() throws Throwable {
+        final int appUid = getUid();
+
+        // Verify MobileBytesTransfer, passing a ThrowingPredicate that verifies contents of
+        // corresponding atom type to prevent code duplication. The passed predicate returns
+        // true if the atom of appUid is found, false otherwise, and throws an exception if
+        // contents are not expected.
+        doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_FIELD_NUMBER, (atom) -> {
+            final AtomsProto.MobileBytesTransfer data = ((Atom) atom).getMobileBytesTransfer();
+            if (data.getUid() == appUid) {
+                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
+                        data.getRxPackets(), data.getTxPackets());
+                return true; // found
+            }
+            return false;
+        });
+    }
+
+    public void testMobileBytesTransferByFgBg() throws Throwable {
+        final int appUid = getUid();
+
+        doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_BY_FG_BG_FIELD_NUMBER, (atom) -> {
+            final AtomsProto.MobileBytesTransferByFgBg data =
+                    ((Atom) atom).getMobileBytesTransferByFgBg();
+            if (data.getUid() == appUid && data.getIsForeground()) {
+                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
+                        data.getRxPackets(), data.getTxPackets());
+                return true; // found
+            }
+            return false;
+        });
+    }
+
+    private void assertSubscriptionInfo(AtomsProto.DataUsageBytesTransfer data) {
+        assertThat(data.getSimMcc()).matches("^\\d{3}$");
+        assertThat(data.getSimMnc()).matches("^\\d{2,3}$");
+        assertThat(data.getCarrierId()).isNotEqualTo(-1); // TelephonyManager#UNKNOWN_CARRIER_ID
+    }
+
+    private void doTestDataUsageBytesTransferEnabled(boolean enable) throws Throwable {
+        // Set value to enable/disable combine subtype.
+        setNetworkStatsCombinedSubTypeEnabled(enable);
+
+        doTestMobileBytesTransferThat(Atom.DATA_USAGE_BYTES_TRANSFER_FIELD_NUMBER, (atom) -> {
+            final AtomsProto.DataUsageBytesTransfer data =
+                    ((Atom) atom).getDataUsageBytesTransfer();
+            final boolean ratTypeEqualsToUnknown =
+                    (data.getRatType() == NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
+            final boolean ratTypeGreaterThanUnknown =
+                    (data.getRatType() > NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
+
+            if ((data.getState() == 1 /*NetworkStats.SET_FOREGROUND*/)
+                    && ((enable && ratTypeEqualsToUnknown)
+                    || (!enable && ratTypeGreaterThanUnknown))) {
+                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
+                        data.getRxPackets(), data.getTxPackets());
+                // Assert that subscription info is valid.
+                assertSubscriptionInfo(data);
+
+                return true; // found
+            }
+            return false;
+        });
+    }
+
+    public void testDataUsageBytesTransfer() throws Throwable {
+        final boolean oldSubtypeCombined = getNetworkStatsCombinedSubTypeEnabled();
+
+        doTestDataUsageBytesTransferEnabled(true);
+
+        // Remove config from memory and disk to clear the history.
+        removeConfig(CONFIG_ID);
+        getReportList(); // Clears data.
+
+        doTestDataUsageBytesTransferEnabled(false);
+
+        // Restore to original default value.
+        setNetworkStatsCombinedSubTypeEnabled(oldSubtypeCombined);
+    }
+
+    // TODO(b/157651730): Determine how to test tag and metered state within atom.
+    public void testBytesTransferByTagAndMetered() throws Throwable {
+        final int appUid = getUid();
+        final int atomId = Atom.BYTES_TRANSFER_BY_TAG_AND_METERED_FIELD_NUMBER;
+
+        doTestMobileBytesTransferThat(atomId, (atom) -> {
+            final AtomsProto.BytesTransferByTagAndMetered data =
+                    ((Atom) atom).getBytesTransferByTagAndMetered();
+            if (data.getUid() == appUid && data.getTag() == 0 /*app traffic generated on tag 0*/) {
+                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
+                        data.getRxPackets(), data.getTxPackets());
+                return true; // found
+            }
+            return false;
+        });
+    }
+
+    public void testPushedBlobStoreStats() throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, Atom.BLOB_COMMITTED_FIELD_NUMBER, false);
+        addAtomEvent(conf, Atom.BLOB_LEASED_FIELD_NUMBER, false);
+        addAtomEvent(conf, Atom.BLOB_OPENED_FIELD_NUMBER, false);
+        uploadConfig(conf);
+
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testBlobStore");
+
+        List<EventMetricData> data = getEventMetricDataList();
+        assertThat(data).hasSize(3);
+
+        BlobCommitted blobCommitted = data.get(0).getAtom().getBlobCommitted();
+        final long blobId = blobCommitted.getBlobId();
+        final long blobSize = blobCommitted.getSize();
+        assertThat(blobCommitted.getUid()).isEqualTo(getUid());
+        assertThat(blobId).isNotEqualTo(0);
+        assertThat(blobSize).isNotEqualTo(0);
+        assertThat(blobCommitted.getResult()).isEqualTo(BlobCommitted.Result.SUCCESS);
+
+        BlobLeased blobLeased = data.get(1).getAtom().getBlobLeased();
+        assertThat(blobLeased.getUid()).isEqualTo(getUid());
+        assertThat(blobLeased.getBlobId()).isEqualTo(blobId);
+        assertThat(blobLeased.getSize()).isEqualTo(blobSize);
+        assertThat(blobLeased.getResult()).isEqualTo(BlobLeased.Result.SUCCESS);
+
+        BlobOpened blobOpened = data.get(2).getAtom().getBlobOpened();
+        assertThat(blobOpened.getUid()).isEqualTo(getUid());
+        assertThat(blobOpened.getBlobId()).isEqualTo(blobId);
+        assertThat(blobOpened.getSize()).isEqualTo(blobSize);
+        assertThat(blobOpened.getResult()).isEqualTo(BlobOpened.Result.SUCCESS);
+    }
+
+    // Constants that match the constants for AtomTests#testBlobStore
+    private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
+    private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
+    private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
+    private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
+
+    public void testPulledBlobStoreStats() throws Exception {
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config,
+                Atom.BLOB_INFO_FIELD_NUMBER,
+                null);
+        uploadConfig(config);
+
+        final long testStartTimeMs = System.currentTimeMillis();
+        Thread.sleep(WAIT_TIME_SHORT);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testBlobStore");
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Add commit callback time to test end time to account for async execution
+        final long testEndTimeMs =
+                System.currentTimeMillis() + BLOB_COMMIT_CALLBACK_TIMEOUT_SEC * 1000;
+
+        // Find the BlobInfo for the blob created in the test run
+        AtomsProto.BlobInfo blobInfo = null;
+        for (Atom atom : getGaugeMetricDataList()) {
+            if (atom.hasBlobInfo()) {
+                final AtomsProto.BlobInfo temp = atom.getBlobInfo();
+                if (temp.getCommitters().getCommitter(0).getUid() == getUid()) {
+                    blobInfo = temp;
+                    break;
+                }
+            }
+        }
+        assertThat(blobInfo).isNotNull();
+
+        assertThat(blobInfo.getSize()).isEqualTo(BLOB_FILE_SIZE_BYTES);
+
+        // Check that expiry time is reasonable
+        assertThat(blobInfo.getExpiryTimestampMillis()).isGreaterThan(
+                testStartTimeMs + BLOB_EXPIRY_DURATION_MS);
+        assertThat(blobInfo.getExpiryTimestampMillis()).isLessThan(
+                testEndTimeMs + BLOB_EXPIRY_DURATION_MS);
+
+        // Check that commit time is reasonable
+        final long commitTimeMs = blobInfo.getCommitters().getCommitter(
+                0).getCommitTimestampMillis();
+        assertThat(commitTimeMs).isGreaterThan(testStartTimeMs);
+        assertThat(commitTimeMs).isLessThan(testEndTimeMs);
+
+        // Check that WHITELIST and PRIVATE access mode flags are set
+        assertThat(blobInfo.getCommitters().getCommitter(0).getAccessMode()).isEqualTo(0b1001);
+        assertThat(blobInfo.getCommitters().getCommitter(0).getNumWhitelistedPackage()).isEqualTo(
+                1);
+
+        assertThat(blobInfo.getLeasees().getLeaseeCount()).isGreaterThan(0);
+        assertThat(blobInfo.getLeasees().getLeasee(0).getUid()).isEqualTo(getUid());
+
+        // Check that lease expiry time is reasonable
+        final long leaseExpiryMs = blobInfo.getLeasees().getLeasee(
+                0).getLeaseExpiryTimestampMillis();
+        assertThat(leaseExpiryMs).isGreaterThan(testStartTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
+        assertThat(leaseExpiryMs).isLessThan(testEndTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
+    }
+
+    private void assertDataUsageAtomDataExpected(long rxb, long txb, long rxp, long txp) {
+        assertThat(rxb).isGreaterThan(0L);
+        assertThat(txb).isGreaterThan(0L);
+        assertThat(rxp).isGreaterThan(0L);
+        assertThat(txp).isGreaterThan(0L);
+    }
+
+    private void doTestMobileBytesTransferThat(int atomTag, ThrowingPredicate p)
+            throws Throwable {
+        if (!hasFeature(FEATURE_TELEPHONY, true)) return;
+
+        // Get MobileBytesTransfer as a simple gauge metric.
+        final StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, atomTag, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Generate some traffic on mobile network.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testGenerateMobileTraffic");
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Force polling NetworkStatsService to get most updated network stats from lower layer.
+        runActivity("StatsdCtsForegroundActivity", "action", "action.poll_network_stats");
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Pull a report
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final List<Atom> atoms = getGaugeMetricDataList(/*checkTimestampTruncated=*/true);
+        assertThat(atoms.size()).isAtLeast(1);
+
+        boolean foundAppStats = false;
+        for (final Atom atom : atoms) {
+            if (p.accept(atom)) {
+                foundAppStats = true;
+            }
+        }
+        assertWithMessage("uid " + getUid() + " is not found in " + atoms.size() + " atoms")
+                .that(foundAppStats).isTrue();
+    }
+
+    @FunctionalInterface
+    private interface ThrowingPredicate<S, T extends Throwable> {
+        boolean accept(S s) throws T;
+    }
+
+    public void testPackageInstallerV2MetricsReported() throws Throwable {
+        if (!hasFeature(FEATURE_INCREMENTAL_DELIVERY, true)) return;
+        final AtomsProto.PackageInstallerV2Reported report = installPackageUsingV2AndGetReport(
+                new String[]{TEST_INSTALL_APK});
+        assertTrue(report.getIsIncremental());
+        // tests are ran using SHELL_UID and installation will be treated as adb install
+        assertEquals("", report.getPackageName());
+        assertEquals(1, report.getReturnCode());
+        assertTrue(report.getDurationMillis() > 0);
+        assertEquals(getTestFileSize(TEST_INSTALL_APK), report.getApksSizeBytes());
+
+        getDevice().uninstallPackage(TEST_INSTALL_PACKAGE);
+    }
+
+    public void testPackageInstallerV2MetricsReportedForSplits() throws Throwable {
+        if (!hasFeature(FEATURE_INCREMENTAL_DELIVERY, true)) return;
+
+        final AtomsProto.PackageInstallerV2Reported report = installPackageUsingV2AndGetReport(
+                new String[]{TEST_INSTALL_APK_BASE, TEST_INSTALL_APK_SPLIT});
+        assertTrue(report.getIsIncremental());
+        // tests are ran using SHELL_UID and installation will be treated as adb install
+        assertEquals("", report.getPackageName());
+        assertEquals(1, report.getReturnCode());
+        assertTrue(report.getDurationMillis() > 0);
+        assertEquals(
+                getTestFileSize(TEST_INSTALL_APK_BASE) + getTestFileSize(TEST_INSTALL_APK_SPLIT),
+                report.getApksSizeBytes());
+
+        getDevice().uninstallPackage(TEST_INSTALL_PACKAGE);
+    }
+
+    public void testAppForegroundBackground() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                AppUsageEventOccurred.EventType.MOVE_TO_FOREGROUND_VALUE));
+        Set<Integer> offStates = new HashSet<>(Arrays.asList(
+                AppUsageEventOccurred.EventType.MOVE_TO_BACKGROUND_VALUE));
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(Atom.APP_USAGE_EVENT_OCCURRED_FIELD_NUMBER, false);
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        getDevice().executeShellCommand(String.format(
+                "am start -n '%s' -e %s %s",
+                "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity",
+                "action", ACTION_SHOW_APPLICATION_OVERLAY));
+        final int waitTime = EXTRA_WAIT_TIME_MS + 5_000; // Overlay may need to sit there a while.
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        Function<Atom, Integer> appUsageStateFunction =
+                atom -> atom.getAppUsageEventOccurred().getEventType().getNumber();
+        popUntilFind(data, onStates, appUsageStateFunction); // clear out initial appusage states.s
+        assertStatesOccurred(stateSet, data, 0, appUsageStateFunction);
+    }
+
+    public void testAppForceStopUsageEvent() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                AppUsageEventOccurred.EventType.MOVE_TO_FOREGROUND_VALUE));
+        Set<Integer> offStates = new HashSet<>(Arrays.asList(
+                AppUsageEventOccurred.EventType.MOVE_TO_BACKGROUND_VALUE));
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(Atom.APP_USAGE_EVENT_OCCURRED_FIELD_NUMBER, false);
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        getDevice().executeShellCommand(String.format(
+                "am start -n '%s' -e %s %s",
+                "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity",
+                "action", ACTION_LONG_SLEEP_WHILE_TOP));
+        final int waitTime = EXTRA_WAIT_TIME_MS + 5_000;
+        Thread.sleep(waitTime);
+
+        getDevice().executeShellCommand(String.format(
+                "am force-stop %s",
+                "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity"));
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        Function<Atom, Integer> appUsageStateFunction =
+                atom -> atom.getAppUsageEventOccurred().getEventType().getNumber();
+        popUntilFind(data, onStates, appUsageStateFunction); // clear out initial appusage states.
+        assertStatesOccurred(stateSet, data, 0, appUsageStateFunction);
+    }
+
+    private AtomsProto.PackageInstallerV2Reported installPackageUsingV2AndGetReport(
+            String[] apkNames) throws Exception {
+        createAndUploadConfig(Atom.PACKAGE_INSTALLER_V2_REPORTED_FIELD_NUMBER);
+        Thread.sleep(WAIT_TIME_SHORT);
+        installPackageUsingIncremental(apkNames, TEST_REMOTE_DIR);
+        assertTrue(getDevice().isPackageInstalled(TEST_INSTALL_PACKAGE));
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<AtomsProto.PackageInstallerV2Reported> reports = new ArrayList<>();
+        for(EventMetricData data : getEventMetricDataList()) {
+            if (data.getAtom().hasPackageInstallerV2Reported()) {
+                reports.add(data.getAtom().getPackageInstallerV2Reported());
+            }
+        }
+        assertEquals(1, reports.size());
+        return reports.get(0);
+    }
+
+    private void installPackageUsingIncremental(String[] apkNames, String remoteDirPath)
+            throws Exception {
+        getDevice().executeShellCommand("mkdir " + remoteDirPath);
+        String[] remoteApkPaths = new String[apkNames.length];
+        for (int i = 0; i < remoteApkPaths.length; i++) {
+            remoteApkPaths[i] = pushApkToRemote(apkNames[i], remoteDirPath);
+        }
+        getDevice().executeShellCommand(
+                "pm install-incremental -t -g " + String.join(" ", remoteApkPaths));
+    }
+
+    private String pushApkToRemote(String apkName, String remoteDirPath)
+            throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File apk = buildHelper.getTestFile(apkName);
+        final String remoteApkPath = remoteDirPath + "/" + apk.getName();
+        assertTrue(getDevice().pushFile(apk, remoteApkPath));
+        assertNotNull(apk);
+        return remoteApkPath;
+    }
+
+    private long getTestFileSize(String fileName) throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File file = buildHelper.getTestFile(fileName);
+        return file.length();
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTestsTemp.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTestsTemp.java
new file mode 100644
index 0000000..176fa1a
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTestsTemp.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.statsd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public final class UidAtomTestsTemp extends DeviceTestCase implements IBuildReceiver {
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallStatsdTestApp(getDevice());
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    /**
+     * Tests that statsd correctly maps isolated uids to host uids by verifying that atoms logged
+     * from an isolated process are seen as coming from their host process.
+     */
+    public void testIsolatedToHostUidMapping() throws Exception {
+        StatsdConfig.Builder config =
+                ConfigUtils.createConfigBuilder(DeviceUtils.STATSD_ATOM_TEST_PKG);
+        ConfigUtils.addEventMetricForUidAtom(config, Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER,
+                /*uidInAttributionChain=*/false, DeviceUtils.STATSD_ATOM_TEST_PKG);
+        ConfigUtils.uploadConfig(getDevice(), config);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Create an isolated service from which an AppBreadcrumbReported atom is logged.
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests",
+                "testIsolatedProcessService");
+
+        // Verify correctness of data.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data).hasSize(1);
+        AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
+        assertThat(atom.getUid()).isEqualTo(DeviceUtils.getStatsdTestAppUid(getDevice()));
+        assertThat(atom.getLabel()).isEqualTo(0);
+        assertThat(atom.getState()).isEqualTo(AppBreadcrumbReported.State.START);
+    }
+
+    public void testCpuTimePerUid() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
+
+        StatsdConfig.Builder config =
+                ConfigUtils.createConfigBuilder(DeviceUtils.STATSD_ATOM_TEST_PKG);
+        ConfigUtils.addGaugeMetricForUidAtom(config, Atom.CPU_TIME_PER_UID_FIELD_NUMBER,
+                /*uidInAttributionChain=*/false, DeviceUtils.STATSD_ATOM_TEST_PKG);
+        ConfigUtils.uploadConfig(getDevice(), config);
+
+        // Do some trivial work on the app
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testSimpleCpu");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Trigger atom pull
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // Verify correctness of data
+        List<Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+        boolean found = false;
+        int appUid = DeviceUtils.getStatsdTestAppUid(getDevice());
+        for (Atom atom : atoms) {
+            assertThat(atom.getCpuTimePerUid().getUid()).isEqualTo(appUid);
+            assertThat(atom.getCpuTimePerUid().getUserTimeMicros()).isGreaterThan(0L);
+            assertThat(atom.getCpuTimePerUid().getSysTimeMicros()).isGreaterThan(0L);
+            found = true;
+        }
+        assertWithMessage("Found no CpuTimePerUid atoms from uid " + appUid).that(found).isTrue();
+    }
+}
diff --git a/hostsidetests/sustainedperf/TEST_MAPPING b/hostsidetests/sustainedperf/TEST_MAPPING
new file mode 100644
index 0000000..b3f1cff
--- /dev/null
+++ b/hostsidetests/sustainedperf/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSustainedPerformanceHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/sustainedperf/app/AndroidManifest.xml b/hostsidetests/sustainedperf/app/AndroidManifest.xml
index eff4a7f..7ba4a2b 100755
--- a/hostsidetests/sustainedperf/app/AndroidManifest.xml
+++ b/hostsidetests/sustainedperf/app/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.test.app">
+     package="android.test.app">
 
     <application>
-        <activity android:name=".DeviceTestActivity" >
+        <activity android:name=".DeviceTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/sustainedperf/dhrystone/dhry_1.c b/hostsidetests/sustainedperf/dhrystone/dhry_1.c
index 3682052..fe51acb 100644
--- a/hostsidetests/sustainedperf/dhrystone/dhry_1.c
+++ b/hostsidetests/sustainedperf/dhrystone/dhry_1.c
@@ -82,8 +82,8 @@
         Enumeration     Enum_Loc;
         Str_30          Str_1_Loc;
         Str_30          Str_2_Loc;
-  REG   int             Run_Index;
-  REG   int             Number_Of_Runs;
+  REG   long            Run_Index;
+  REG   long            Number_Of_Runs;
 
   /* Initializations */
 
@@ -103,8 +103,8 @@
         /* Arr_2_Glob [8][7] would have an undefined value.             */
         /* Warning: With 16-Bit processors and Number_Of_Runs > 32000,  */
         /* overflow may occur for this array element.                   */
-     int n;
-     scanf ("%d", &n);
+     long n;
+     scanf ("%ld", &n);
      Number_Of_Runs = n;
 
 
diff --git a/hostsidetests/sustainedperf/shadertoy_android/AndroidManifest.xml b/hostsidetests/sustainedperf/shadertoy_android/AndroidManifest.xml
index 77dee18..91cf3ed 100644
--- a/hostsidetests/sustainedperf/shadertoy_android/AndroidManifest.xml
+++ b/hostsidetests/sustainedperf/shadertoy_android/AndroidManifest.xml
@@ -1,41 +1,41 @@
-<?xml version="1.0" encoding="utf-8"?>

-<!--

-/*

-**

-** Copyright 2009, The Android Open Source Project

-**

-** Licensed under the Apache License, Version 2.0 (the "License");

-** you may not use this file except in compliance with the License.

-** You may obtain a copy of the License at

-**

-**     http://www.apache.org/licenses/LICENSE-2.0

-**

-** Unless required by applicable law or agreed to in writing, software

-** distributed under the License is distributed on an "AS IS" BASIS,

-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

-** See the License for the specific language governing permissions and

-** limitations under the License.

-*/

--->

-

-<manifest xmlns:android="http://schemas.android.com/apk/res/android"

-    package="com.android.gputest">

-    <application

-            android:label="@string/gpustresstest_activity">

-        <activity android:name="GPUStressTestActivity"

-                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"

-                android:launchMode="singleTask"

-                android:configChanges="orientation|keyboardHidden">

-            <intent-filter>

-                <action android:name="android.intent.action.MAIN" />

-                <category android:name="android.intent.category.LAUNCHER" />

-            </intent-filter>

-        </activity>

-    </application>

-    <uses-feature android:glEsVersion="0x00020000"/>

-    <uses-sdk android:minSdkVersion="5"/>

-    <uses-permission android:name="android.permission.INTERNET" />

-    <uses-permission android:name="com.qti.permission.PROFILER" />

-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

-</manifest>

+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.gputest">
+    <application android:label="@string/gpustresstest_activity">
+        <activity android:name="GPUStressTestActivity"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+             android:launchMode="singleTask"
+             android:configChanges="orientation|keyboardHidden"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+    <uses-feature android:glEsVersion="0x00020000"/>
+    <uses-sdk android:minSdkVersion="5"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="com.qti.permission.PROFILER"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+</manifest>
diff --git a/hostsidetests/systemui/AndroidTest.xml b/hostsidetests/systemui/AndroidTest.xml
index 1fa4c74..dd1d8a6 100644
--- a/hostsidetests/systemui/AndroidTest.xml
+++ b/hostsidetests/systemui/AndroidTest.xml
@@ -22,8 +22,6 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSystemUiDeviceApp.apk" />
-        <option name="test-file-name" value="CtsSystemUiDeviceAudioRecorderAppAudioRecord.apk" />
-        <option name="test-file-name" value="CtsSystemUiDeviceAudioRecorderAppMediaRecorder.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsSystemUiHostTestCases.jar" />
diff --git a/hostsidetests/systemui/TEST_MAPPING b/hostsidetests/systemui/TEST_MAPPING
new file mode 100644
index 0000000..6680744
--- /dev/null
+++ b/hostsidetests/systemui/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSystemUiHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/systemui/app/AndroidManifest.xml b/hostsidetests/systemui/app/AndroidManifest.xml
index 30be410..3ca3ef5 100755
--- a/hostsidetests/systemui/app/AndroidManifest.xml
+++ b/hostsidetests/systemui/app/AndroidManifest.xml
@@ -16,35 +16,36 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.systemui.cts">
+     package="android.systemui.cts">
 
     <application>
         <service android:name=".TestTileService"
-            android:icon="@android:drawable/ic_delete"
-            android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+             android:icon="@android:drawable/ic_delete"
+             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.quicksettings.action.QS_TILE" />
+                <action android:name="android.service.quicksettings.action.QS_TILE"/>
             </intent-filter>
         </service>
 
         <service android:name=".TestActiveTileService"
-            android:icon="@android:drawable/ic_delete"
-            android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+             android:icon="@android:drawable/ic_delete"
+             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.quicksettings.action.QS_TILE" />
+                <action android:name="android.service.quicksettings.action.QS_TILE"/>
             </intent-filter>
             <meta-data android:name="android.service.quicksettings.ACTIVE_TILE"
-                android:value="true" />
+                 android:value="true"/>
         </service>
 
-        <receiver
-            android:name=".TestActiveTileService$Receiver">
+        <receiver android:name=".TestActiveTileService$Receiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.sysui.testtile.REQUEST_LISTENING" />
+                <action android:name="android.sysui.testtile.REQUEST_LISTENING"/>
             </intent-filter>
         </receiver>
 
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp b/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp
deleted file mode 100644
index 306a25e..0000000
--- a/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-android_test_helper_app {
-    name: "CtsSystemUiDeviceAudioRecorderAppAudioRecord",
-    static_libs: ["CtsSystemUiDeviceAudioRecorderBase"],
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-    sdk_version: "current",
-}
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp b/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp
deleted file mode 100644
index e97729a..0000000
--- a/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-android_test_helper_app {
-    name: "CtsSystemUiDeviceAudioRecorderAppMediaRecorder",
-    static_libs: ["CtsSystemUiDeviceAudioRecorderBase"],
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-    sdk_version: "current",
-}
diff --git a/hostsidetests/systemui/audiorecorder_base/Android.bp b/hostsidetests/systemui/audiorecorder_base/Android.bp
deleted file mode 100644
index ba9c7e6..0000000
--- a/hostsidetests/systemui/audiorecorder_base/Android.bp
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-android_library {
-    name: "CtsSystemUiDeviceAudioRecorderBase",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
-    sdk_version: "current",
-}
diff --git a/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java b/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java
deleted file mode 100644
index 674226c..0000000
--- a/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.host.systemui;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.server.wm.ActivityRecordProto;
-import com.android.server.wm.DisplayAreaProto;
-import com.android.server.wm.DisplayContentProto;
-import com.android.server.wm.RootWindowContainerProto;
-import com.android.server.wm.TaskProto;
-import com.android.server.wm.WindowContainerChildProto;
-import com.android.server.wm.WindowContainerProto;
-import com.android.server.wm.WindowManagerServiceDumpProto;
-import com.android.server.wm.WindowStateProto;
-import com.android.server.wm.WindowTokenProto;
-import com.android.tradefed.device.CollectingByteOutputReceiver;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import org.junit.After;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Ignore
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class TvMicrophoneCaptureIndicatorTest extends BaseHostJUnit4Test {
-    private static final String SHELL_AM_START_FG_SERVICE =
-            "am start-foreground-service -n %s -a %s";
-    private static final String SHELL_AM_FORCE_STOP =
-            "am force-stop %s";
-    private static final String SHELL_DUMPSYS_WINDOW = "dumpsys window --proto";
-    private static final String SHELL_PID_OF = "pidof %s";
-
-    private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
-
-    private static final String AUDIO_RECORDER_AR_PACKAGE_NAME =
-            "android.systemui.cts.audiorecorder.audiorecord";
-    private static final String AUDIO_RECORDER_MR_PACKAGE_NAME =
-            "android.systemui.cts.audiorecorder.mediarecorder";
-    private static final String AUDIO_RECORDER_AR_SERVICE_COMPONENT =
-            AUDIO_RECORDER_AR_PACKAGE_NAME + "/.AudioRecorderService";
-    private static final String AUDIO_RECORDER_MR_SERVICE_COMPONENT =
-            AUDIO_RECORDER_MR_PACKAGE_NAME + "/.AudioRecorderService";
-    private static final String AUDIO_RECORDER_ACTION_START =
-            "android.systemui.cts.audiorecorder.ACTION_START";
-    private static final String AUDIO_RECORDER_ACTION_STOP =
-            "android.systemui.cts.audiorecorder.ACTION_STOP";
-    private static final String AUDIO_RECORDER_ACTION_THROW =
-            "android.systemui.cts.audiorecorder.ACTION_THROW";
-
-    private static final String SHELL_AR_START_REC = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_AR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_START);
-    private static final String SHELL_AR_STOP_REC = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_AR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_STOP);
-    private static final String SHELL_MR_START_REC = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_MR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_START);
-    private static final String SHELL_MR_STOP_REC = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_MR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_STOP);
-    private static final String SHELL_AR_FORCE_STOP = String.format(SHELL_AM_FORCE_STOP,
-            AUDIO_RECORDER_AR_PACKAGE_NAME);
-    private static final String SHELL_MR_FORCE_STOP = String.format(SHELL_AM_FORCE_STOP,
-            AUDIO_RECORDER_MR_PACKAGE_NAME);
-    private static final String SHELL_AR_THROW = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_AR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_THROW);
-    private static final String SHELL_MR_THROW = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_MR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_THROW);
-
-    private static final String WINDOW_TITLE_MIC_INDICATOR = "MicrophoneCaptureIndicator";
-
-    private static final long ONE_SECOND = 1000L;
-    private static final long THREE_SECONDS = 3 * ONE_SECOND;
-    private static final long FIVE_SECONDS = 5 * ONE_SECOND;
-    private static final long THREE_HUNDRED_MILLISECONDS = (long) (0.3 * ONE_SECOND);
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingAudioRecordApi() throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
-                SHELL_AR_STOP_REC);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingMediaRecorderApi() throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
-                SHELL_MR_STOP_REC);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingAudioRecordApiAndForceStopped()
-            throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
-                SHELL_AR_FORCE_STOP);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingMediaRecorderApiAndForceStopped()
-            throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
-                SHELL_MR_FORCE_STOP);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingAudioRecordApiAndCrashed() throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
-                SHELL_AR_THROW);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingMediaRecorderApiAndCrashed() throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
-                SHELL_MR_THROW);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingBothApisSimultaneously() throws Exception {
-        assumeTrue("Not running on a Leanback (TV) device",
-                getDevice().hasFeature(FEATURE_LEANBACK_ONLY));
-
-        // Check that the indicator isn't shown initially
-        assertIndicatorInvisible();
-
-        // Start recording using MediaRecorder API
-        getDevice().executeShellCommand(SHELL_MR_START_REC);
-
-        // Wait for the application to be launched
-        waitForProcessToComeAlive(AUDIO_RECORDER_MR_PACKAGE_NAME);
-
-        // Wait for a second, and then check that the indicator is shown
-        Thread.sleep(ONE_SECOND);
-        assertIndicatorVisible();
-
-        // Start recording using AudioRecord API
-        getDevice().executeShellCommand(SHELL_AR_START_REC);
-
-        // Wait for the application to be launched
-        waitForProcessToComeAlive(AUDIO_RECORDER_AR_PACKAGE_NAME);
-
-        // Check that the indicator is still shown
-        assertIndicatorVisible();
-
-        // Check 3 more times that the indicator remains shown
-        for (int i = 0; i < 3; i++) {
-            Thread.sleep(ONE_SECOND);
-            assertIndicatorVisible();
-        }
-
-        // Stop recording using MediaRecorder API
-        getDevice().executeShellCommand(SHELL_MR_STOP_REC);
-
-        // check that the indicator is still shown
-        assertIndicatorVisible();
-
-        // Check 3 more times that the indicator remains shown
-        for (int i = 0; i < 3; i++) {
-            Thread.sleep(ONE_SECOND);
-            assertIndicatorVisible();
-        }
-
-        // Stop recording using AudioRecord API
-        getDevice().executeShellCommand(SHELL_AR_STOP_REC);
-
-        // Wait for five seconds and make sure that the indicator is not shown
-        Thread.sleep(FIVE_SECONDS);
-        assertIndicatorInvisible();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        // Kill both apps
-        getDevice().executeShellCommand(SHELL_AR_FORCE_STOP);
-        getDevice().executeShellCommand(SHELL_MR_FORCE_STOP);
-    }
-
-    private void runSimpleStartStopTestRoutine(String packageName, String startCommand,
-            String stopCommand) throws Exception {
-        assumeTrue("Not running on a Leanback (TV) device",
-                getDevice().hasFeature(FEATURE_LEANBACK_ONLY));
-
-        // Check that the indicator isn't shown initially
-        assertIndicatorInvisible();
-
-        // Start recording using AudioRecord API
-        getDevice().executeShellCommand(startCommand);
-
-        // Wait for the application to be launched
-        waitForProcessToComeAlive(packageName);
-
-        // Wait for a second, and then check that the indicator is shown, repeat 2 more times
-        for (int i = 0; i < 3; i++) {
-            Thread.sleep(ONE_SECOND);
-            assertIndicatorVisible();
-        }
-
-        // Stop recording (this may either send a command to the app to stop recording or command
-        // to crash or force-stop the app)
-        getDevice().executeShellCommand(stopCommand);
-
-        // Wait for five seconds and make sure that the indicator is not shown
-        Thread.sleep(FIVE_SECONDS);
-        assertIndicatorInvisible();
-    }
-
-    private void waitForProcessToComeAlive(String appPackageName) throws Exception {
-        final String pidofCommand = String.format(SHELL_PID_OF, appPackageName);
-
-        long waitTime = 0;
-        while (waitTime < THREE_SECONDS) {
-            Thread.sleep(THREE_HUNDRED_MILLISECONDS);
-
-            final String pid = getDevice().executeShellCommand(pidofCommand).trim();
-            if (!pid.isEmpty()) {
-                // Process is running
-                return;
-            }
-            waitTime += THREE_HUNDRED_MILLISECONDS;
-        }
-
-        fail("The process for " + appPackageName
-                + " should have come alive within 3 secs of launching the app.");
-    }
-
-    private void assertIndicatorVisible() throws Exception {
-        final WindowStateProto window = getMicCaptureIndicatorWindow();
-
-        assertNotNull("\"MicrophoneCaptureIndicator\" window does not exist", window);
-        assertTrue("\"MicrophoneCaptureIndicator\" window is not visible",
-                window.getIsVisible());
-        assertTrue("\"MicrophoneCaptureIndicator\" window is not on screen",
-                window.getIsOnScreen());
-    }
-
-    private void assertIndicatorInvisible() throws Exception {
-        final WindowStateProto window = getMicCaptureIndicatorWindow();
-        if (window == null) {
-            // If window is not present, that's fine, there is no need to check anything else.
-            return;
-        }
-
-        assertFalse("\"MicrophoneCaptureIndicator\" window shouldn't be visible",
-                window.getIsVisible());
-        assertFalse("\"MicrophoneCaptureIndicator\" window shouldn't be present on screen",
-                window.getIsOnScreen());
-    }
-
-    private WindowStateProto getMicCaptureIndicatorWindow() throws Exception {
-        final WindowManagerServiceDumpProto dump = getDump();
-        final RootWindowContainerProto rootWindowContainer = dump.getRootWindowContainer();
-        final WindowContainerProto windowContainer = rootWindowContainer.getWindowContainer();
-
-        final List<WindowStateProto> windows = new ArrayList<>();
-        collectWindowStates(windowContainer, windows);
-
-        for (WindowStateProto window : windows) {
-            final String title = window.getIdentifier().getTitle();
-            if (WINDOW_TITLE_MIC_INDICATOR.equals(title)) {
-                return window;
-            }
-        }
-        return null;
-    }
-
-    private WindowManagerServiceDumpProto getDump() throws Exception {
-        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
-        getDevice().executeShellCommand(SHELL_DUMPSYS_WINDOW, receiver);
-        return WindowManagerServiceDumpProto.parser().parseFrom(receiver.getOutput());
-    }
-
-    /**
-     * This methods implements a DFS that goes through a tree of window containers and collects all
-     * the WindowStateProto-s.
-     *
-     * WindowContainer is generic class that can hold windows directly or through its children in a
-     * hierarchy form. WindowContainer's children are WindowContainer as well. This forms a tree of
-     * WindowContainers.
-     *
-     * There are a few classes that extend WindowContainer: Task, DisplayContent, WindowToken etc.
-     * The one we are interested in is WindowState.
-     * Since Proto does not have concept of inheritance, {@link TaskProto}, {@link WindowTokenProto}
-     * etc hold a reference to a {@link WindowContainerProto} (in java code would be {@code super}
-     * reference).
-     * {@link WindowContainerProto} may a have a number of children of type
-     * {@link WindowContainerChildProto}, which represents a generic child of a WindowContainer: a
-     * WindowContainer can have multiple children of different types stored as a
-     * {@link WindowContainerChildProto}, but each instance of {@link WindowContainerChildProto} can
-     * only contain a single type.
-     *
-     * For details see /frameworks/base/core/proto/android/server/windowmanagerservice.proto
-     */
-    private void collectWindowStates(WindowContainerProto windowContainer, List<WindowStateProto> out) {
-        if (windowContainer == null) return;
-
-        final List<WindowContainerChildProto> children = windowContainer.getChildrenList();
-        for (WindowContainerChildProto child : children) {
-            if (child.hasWindowContainer()) {
-                collectWindowStates(child.getWindowContainer(), out);
-            } else if (child.hasDisplayContent()) {
-                final DisplayContentProto displayContent = child.getDisplayContent();
-                for (WindowTokenProto windowToken : displayContent.getOverlayWindowsList()) {
-                    collectWindowStates(windowToken.getWindowContainer(), out);
-                }
-                if (displayContent.hasRootDisplayArea()) {
-                    final DisplayAreaProto displayArea = displayContent.getRootDisplayArea();
-                    collectWindowStates(displayArea.getWindowContainer(), out);
-                }
-                collectWindowStates(displayContent.getWindowContainer(), out);
-            } else if (child.hasDisplayArea()) {
-                final DisplayAreaProto displayArea = child.getDisplayArea();
-                collectWindowStates(displayArea.getWindowContainer(), out);
-            } else if (child.hasTask()) {
-                final TaskProto task = child.getTask();
-                collectWindowStates(task.getWindowContainer(), out);
-            } else if (child.hasActivity()) {
-                final ActivityRecordProto activity = child.getActivity();
-                if (activity.hasWindowToken()) {
-                    final WindowTokenProto windowToken = activity.getWindowToken();
-                    collectWindowStates(windowToken.getWindowContainer(), out);
-                }
-            } else if (child.hasWindowToken()) {
-                final WindowTokenProto windowToken = child.getWindowToken();
-                collectWindowStates(windowToken.getWindowContainer(), out);
-            } else if (child.hasWindow()) {
-                final WindowStateProto window = child.getWindow();
-                // We found a Window!
-                out.add(window);
-                // ... but still aren't done
-                collectWindowStates(window.getWindowContainer(), out);
-            }
-        }
-    }
-}
diff --git a/hostsidetests/testharness/app/AndroidManifest.xml b/hostsidetests/testharness/app/AndroidManifest.xml
index 421894d..8fd4ad1 100755
--- a/hostsidetests/testharness/app/AndroidManifest.xml
+++ b/hostsidetests/testharness/app/AndroidManifest.xml
@@ -16,20 +16,19 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.testharness.app">
+     package="android.testharness.app">
 
     <application>
-        <activity android:name=".TestHarnessActivity" >
+        <activity android:name=".TestHarnessActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.testharness.app" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.testharness.app"/>
 
 </manifest>
-
diff --git a/hostsidetests/theme/app/AndroidManifest.xml b/hostsidetests/theme/app/AndroidManifest.xml
index 7487a05..49f2d84 100755
--- a/hostsidetests/theme/app/AndroidManifest.xml
+++ b/hostsidetests/theme/app/AndroidManifest.xml
@@ -16,29 +16,30 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.theme.app">
+     package="android.theme.app">
 
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
 
     <application android:requestLegacyExternalStorage="true">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name=".ThemeDeviceActivity"
-                  android:screenOrientation="portrait">
+             android:screenOrientation="portrait"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".GenerateImagesActivity"
-                  android:screenOrientation="portrait"
-                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|uiMode"
-                  android:exported="true" />
+             android:screenOrientation="portrait"
+             android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|uiMode"
+             android:exported="true"/>
     </application>
 
     <!--  self-instrumenting test package. -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.theme.app"
-                     android:label="Generates Theme reference images"/>
+         android:targetPackage="android.theme.app"
+         android:label="Generates Theme reference images"/>
 
 </manifest>
diff --git a/hostsidetests/trustedvoice/TEST_MAPPING b/hostsidetests/trustedvoice/TEST_MAPPING
new file mode 100644
index 0000000..fb71ad2
--- /dev/null
+++ b/hostsidetests/trustedvoice/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTrustedVoiceHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/trustedvoice/app/AndroidManifest.xml b/hostsidetests/trustedvoice/app/AndroidManifest.xml
index f54af61..7a0d23c 100755
--- a/hostsidetests/trustedvoice/app/AndroidManifest.xml
+++ b/hostsidetests/trustedvoice/app/AndroidManifest.xml
@@ -16,18 +16,18 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.trustedvoice.app">
+     package="android.trustedvoice.app">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <application>
         <activity android:name=".TrustedVoiceActivity"
-                  android:turnScreenOn="true">
+             android:turnScreenOn="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/tv/TEST_MAPPING b/hostsidetests/tv/TEST_MAPPING
new file mode 100644
index 0000000..56367b9
--- /dev/null
+++ b/hostsidetests/tv/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsHostsideTvTests"
+    }
+  ]
+}
diff --git a/hostsidetests/tv/app/AndroidManifest.xml b/hostsidetests/tv/app/AndroidManifest.xml
index bec7daa..09ac6a3 100644
--- a/hostsidetests/tv/app/AndroidManifest.xml
+++ b/hostsidetests/tv/app/AndroidManifest.xml
@@ -15,22 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.tv.hostside">
+     package="com.android.cts.tv.hostside">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <service android:name=".StubTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.tv.hostside" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.tv.hostside"/>
 
 </manifest>
diff --git a/hostsidetests/tv/app2/AndroidManifest.xml b/hostsidetests/tv/app2/AndroidManifest.xml
index 5b0f7b6..66a6d1b 100644
--- a/hostsidetests/tv/app2/AndroidManifest.xml
+++ b/hostsidetests/tv/app2/AndroidManifest.xml
@@ -15,20 +15,20 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.tv.hostside.app2">
+     package="com.android.cts.tv.hostside.app2">
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="com.android.cts.tv.hostside.app2.TvViewMonitorActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="com.android.cts.tv.hostside.app2.TvViewMonitorActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.tv.hostside.app2" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.tv.hostside.app2"/>
 
 </manifest>
diff --git a/hostsidetests/tzdata/TEST_MAPPING b/hostsidetests/tzdata/TEST_MAPPING
new file mode 100644
index 0000000..cb259b1
--- /dev/null
+++ b/hostsidetests/tzdata/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsHostTzDataTests"
+    }
+  ]
+}
diff --git a/hostsidetests/ui/appA/AndroidManifest.xml b/hostsidetests/ui/appA/AndroidManifest.xml
index 8d574d5..4d64df4 100644
--- a/hostsidetests/ui/appA/AndroidManifest.xml
+++ b/hostsidetests/ui/appA/AndroidManifest.xml
@@ -15,29 +15,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.taskswitching.appa"
-        android:targetSandboxVersion="2">
+     package="android.taskswitching.appa"
+     android:targetSandboxVersion="2">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name=".AppAActivity"
-            android:screenOrientation="portrait" >
+        <activity android:name=".AppAActivity"
+             android:screenOrientation="portrait"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="https" />
-                <data android:host="foo.com" />
-                <data android:path="/appa" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="foo.com"/>
+                <data android:path="/appa"/>
             </intent-filter>
 
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.taskswitching.appa" />
+         android:targetPackage="android.taskswitching.appa"/>
 
 </manifest>
diff --git a/hostsidetests/ui/appB/AndroidManifest.xml b/hostsidetests/ui/appB/AndroidManifest.xml
index 9b370c1..ca53ae4 100644
--- a/hostsidetests/ui/appB/AndroidManifest.xml
+++ b/hostsidetests/ui/appB/AndroidManifest.xml
@@ -15,32 +15,32 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.taskswitching.appb"
-        android:targetSandboxVersion="2">
+     package="android.taskswitching.appb"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name=".AppBActivity"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
-            android:screenOrientation="portrait" >
+        <activity android:name=".AppBActivity"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
+             android:screenOrientation="portrait"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="https" />
-                <data android:host="foo.com" />
-                <data android:path="/appb" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="foo.com"/>
+                <data android:path="/appb"/>
             </intent-filter>
 
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.taskswitching.appb" />
+         android:targetPackage="android.taskswitching.appb"/>
 
 </manifest>
diff --git a/hostsidetests/usage/Android.bp b/hostsidetests/usage/Android.bp
index f7e59d2..a10adfd 100644
--- a/hostsidetests/usage/Android.bp
+++ b/hostsidetests/usage/Android.bp
@@ -25,5 +25,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/usage/AndroidTest.xml b/hostsidetests/usage/AndroidTest.xml
index 5c9b845..a29a7b2 100644
--- a/hostsidetests/usage/AndroidTest.xml
+++ b/hostsidetests/usage/AndroidTest.xml
@@ -28,4 +28,9 @@
         <option name="jar" value="CtsAppUsageHostTestCases.jar" />
         <option name="runtime-hint" value="2m" />
     </test>
+
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="android.scheduling"/>
+    </object>
 </configuration>
diff --git a/hostsidetests/usage/TEST_MAPPING b/hostsidetests/usage/TEST_MAPPING
new file mode 100644
index 0000000..6d04088
--- /dev/null
+++ b/hostsidetests/usage/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppUsageHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/usage/app/Android.bp b/hostsidetests/usage/app/Android.bp
index 55e4ac2..55fd413 100644
--- a/hostsidetests/usage/app/Android.bp
+++ b/hostsidetests/usage/app/Android.bp
@@ -21,6 +21,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
 
@@ -35,6 +36,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     aaptflags: [
         "--rename-manifest-package",
diff --git a/hostsidetests/usage/app/AndroidManifest.xml b/hostsidetests/usage/app/AndroidManifest.xml
index 303c26f..2541659 100755
--- a/hostsidetests/usage/app/AndroidManifest.xml
+++ b/hostsidetests/usage/app/AndroidManifest.xml
@@ -16,15 +16,15 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.app.usage.app">
+     package="android.app.usage.app">
 
     <application>
-        <activity android:name="android.app.usage.app.TestActivity">
+        <activity android:name="android.app.usage.app.TestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
 </manifest>
-
diff --git a/hostsidetests/usb/TEST_MAPPING b/hostsidetests/usb/TEST_MAPPING
new file mode 100644
index 0000000..502f1e8
--- /dev/null
+++ b/hostsidetests/usb/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsUsbTests"
+    }
+  ]
+}
diff --git a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
index 01b5d88..3b81acb 100644
--- a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
+++ b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
@@ -145,8 +145,8 @@
                 trim();
         assertEquals("adb serial != ro.serialno" , adbSerial, roSerial);
 
-        CommandResult result = RunUtil.getDefault().runTimedCmd(5000, "lsusb", "-v");
-        assertTrue("lsusb -v failed", result.getStatus() == CommandStatus.SUCCESS);
+        CommandResult result = RunUtil.getDefault().runTimedCmd(15000, "lsusb", "-v");
+        assertEquals("lsusb -v failed", result.getStatus(), CommandStatus.SUCCESS);
         String lsusbOutput = result.getStdout();
         Pattern pattern = Pattern.compile("^\\s+iSerial\\s+\\d+\\s+([a-zA-Z0-9]{6,20})",
                 Pattern.MULTILINE);
diff --git a/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml b/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
index 97ffde6..348a462 100644
--- a/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
+++ b/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
@@ -16,33 +16,34 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.userspacereboot.basic" >
+     package="com.android.cts.userspacereboot.basic">
 
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
     <application>
-        <activity android:name=".LauncherActivity">
+        <activity android:name=".LauncherActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <receiver android:name=".BasicUserspaceRebootTest$BootReceiver"
-                  android:exported="true"
-                  android:directBootAware="true">
+             android:exported="true"
+             android:directBootAware="true">
             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
         <provider android:name=".BasicUserspaceRebootTest$Provider"
-                  android:authorities="com.android.cts.userspacereboot.basic"
-                  android:exported="true"
-                  android:directBootAware="true">
+             android:authorities="com.android.cts.userspacereboot.basic"
+             android:exported="true"
+             android:directBootAware="true">
         </provider>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.userspacereboot.basic"
-                     android:label="Basic userspace reboot device side tests"/>
+         android:targetPackage="com.android.cts.userspacereboot.basic"
+         android:label="Basic userspace reboot device side tests"/>
 </manifest>
diff --git a/hostsidetests/webkit/TEST_MAPPING b/hostsidetests/webkit/TEST_MAPPING
new file mode 100644
index 0000000..fcd8ab5
--- /dev/null
+++ b/hostsidetests/webkit/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsHostsideWebViewTests"
+    }
+  ]
+}
diff --git a/hostsidetests/webkit/app/AndroidManifest.xml b/hostsidetests/webkit/app/AndroidManifest.xml
index b7b17db..2e3ead5 100644
--- a/hostsidetests/webkit/app/AndroidManifest.xml
+++ b/hostsidetests/webkit/app/AndroidManifest.xml
@@ -15,30 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.webkit" android:targetSandboxVersion="2">
+     package="com.android.cts.webkit"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
 
-    <application
-      android:maxRecents="1"
-      android:usesCleartextTraffic="true" >
-        <uses-library android:name="org.apache.http.legacy" />
-        <uses-library android:name="android.test.runner" />
+    <application android:maxRecents="1"
+         android:usesCleartextTraffic="true">
+        <uses-library android:name="org.apache.http.legacy"/>
+        <uses-library android:name="android.test.runner"/>
         <activity android:name=".WebViewStartupCtsActivity"
-            android:label="WebViewStartupCtsActivity"
-            android:screenOrientation="nosensor">
+             android:label="WebViewStartupCtsActivity"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
     </application>
 
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.webkit"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.webkit"/>
 
 </manifest>
diff --git a/hostsidetests/wifibroadcasts/TEST_MAPPING b/hostsidetests/wifibroadcasts/TEST_MAPPING
new file mode 100644
index 0000000..d740fae
--- /dev/null
+++ b/hostsidetests/wifibroadcasts/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsWifiBroadcastsHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/wifibroadcasts/app/AndroidManifest.xml b/hostsidetests/wifibroadcasts/app/AndroidManifest.xml
index 7bc2d00..5e29ffc 100644
--- a/hostsidetests/wifibroadcasts/app/AndroidManifest.xml
+++ b/hostsidetests/wifibroadcasts/app/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.wifibroadcasts.app">
+     package="android.wifibroadcasts.app">
 
     <application>
-        <activity android:name=".WifiBroadcastsDeviceActivity" >
+        <activity android:name=".WifiBroadcastsDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 82044c5..bc666a3 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -16,4 +16,7 @@
     name: "cts-input-lib",
     sdk_version: "test_current",
     srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.rules",
+    ],
 }
\ No newline at end of file
diff --git a/libs/input/src/com/android/cts/input/HidDevice.java b/libs/input/src/com/android/cts/input/HidDevice.java
index 173531a..e00742f 100644
--- a/libs/input/src/com/android/cts/input/HidDevice.java
+++ b/libs/input/src/com/android/cts/input/HidDevice.java
@@ -16,97 +16,96 @@
 
 package com.android.cts.input;
 
-import static android.os.FileUtils.closeQuietly;
 
 import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.hardware.input.InputManager;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.IOException;
-import java.io.OutputStream;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Represents a virtual HID device registered through /dev/uhid.
  */
-public final class HidDevice implements InputManager.InputDeviceListener {
+public final class HidDevice extends VirtualInputDevice {
     private static final String TAG = "HidDevice";
     // hid executable expects "-" argument to read from stdin instead of a file
     private static final String HID_COMMAND = "hid -";
 
-    private final int mId; // // initialized from the json file
+    @GuardedBy("mLock")
+    private List<HidResultData> mResults = new ArrayList<HidResultData>();
 
-    private OutputStream mOutputStream;
-    private Instrumentation mInstrumentation;
+    @Override
+    protected String getShellCommand() {
+        return HID_COMMAND;
+    }
 
-    private volatile CountDownLatch mDeviceAddedSignal; // to wait for onInputDeviceAdded signal
+    @Override
+    protected void readResults() {
+        try {
+            mReader.beginObject();
+            HidResultData result = new HidResultData();
+            while (mReader.hasNext()) {
+                String fieldName = mReader.nextName();
+                if (fieldName.equals("eventId")) {
+                    result.eventId = Byte.decode(mReader.nextString());
+                }
+                if (fieldName.equals("deviceId")) {
+                    result.deviceId = Integer.decode(mReader.nextString());
+                }
+                if (fieldName.equals("reportType")) {
+                    result.reportType = Byte.decode(mReader.nextString());
+                }
+                if (fieldName.equals("reportData")) {
+                    result.reportData = readData();
+                }
+            }
+            mReader.endObject();
+            addResult(result);
+        } catch (IOException ex) {
+            Log.w(TAG, "Exiting JSON Result reader. " + ex);
+        }
+    }
 
     public HidDevice(Instrumentation instrumentation, int deviceId, String registerCommand) {
-        mInstrumentation = instrumentation;
-        setupPipes();
-
-        mInstrumentation.runOnMainSync(new Runnable(){
-            @Override
-            public void run() {
-                InputManager inputManager =
-                        mInstrumentation.getContext().getSystemService(InputManager.class);
-                inputManager.registerInputDeviceListener(HidDevice.this, null);
-            }
-        });
-
-        mId = deviceId;
-        registerInputDevice(registerCommand);
+        super(instrumentation, deviceId, registerCommand);
     }
 
     /**
-     * Register an input device. May cause a failure if the device added notification
-     * is not received within the timeout period
+     * Get hid command return results as list of HidResultData
      *
-     * @param registerCommand The full json command that specifies how to register this device
+     * @return List of HidResultData results
      */
-    private void registerInputDevice(String registerCommand) {
-        mDeviceAddedSignal = new CountDownLatch(1);
-        writeHidCommands(registerCommand.getBytes());
-        try {
-            // Found that in kernel 3.10, the device registration takes a very long time
-            // The wait can be decreased to 2 seconds after kernel 3.10 is no longer supported
-            mDeviceAddedSignal.await(20L, TimeUnit.SECONDS);
-            if (mDeviceAddedSignal.getCount() != 0) {
-                throw new RuntimeException("Did not receive device added notification in time");
+    public synchronized List<HidResultData> getResults(int deviceId, byte eventId)
+            throws IOException {
+        List<HidResultData> results = new ArrayList<HidResultData>();
+        synchronized (mLock) {
+            for (HidResultData result : mResults) {
+                if (deviceId == result.deviceId && eventId == result.eventId) {
+                    results.add(result);
+                }
             }
-        } catch (InterruptedException ex) {
-            throw new RuntimeException(
-                    "Unexpectedly interrupted while waiting for device added notification.");
         }
-        // Even though the device has been added, it still may not be ready to process the events
-        // right away. This seems to be a kernel bug.
-        // Add a small delay here to ensure device is "ready".
-        SystemClock.sleep(500);
+        return results;
     }
 
     /**
-     * Add a delay between processing events.
+     * Add hid command returned HidResultData result
      *
-     * @param milliSeconds The delay in milliseconds.
+     * @param result HidResultData result
      */
-    public void delay(int milliSeconds) {
-        JSONObject json = new JSONObject();
-        try {
-            json.put("command", "delay");
-            json.put("id", mId);
-            json.put("duration", milliSeconds);
-        } catch (JSONException e) {
-            throw new RuntimeException(
-                    "Could not create JSON object to delay " + milliSeconds + " milliseconds");
+    public synchronized void addResult(HidResultData result) {
+        synchronized (mLock) {
+            if (mId == result.deviceId && mResults != null) {
+                mResults.add(result);
+            }
         }
-        writeHidCommands(json.toString().getBytes());
     }
 
     /**
@@ -126,44 +125,7 @@
         } catch (JSONException e) {
             throw new RuntimeException("Could not process HID report: " + report);
         }
-        writeHidCommands(json.toString().getBytes());
+        writeCommands(json.toString().getBytes());
     }
 
-    /**
-     * Close the device, which would cause the associated input device to unregister.
-     */
-    public void close() {
-        closeQuietly(mOutputStream);
-    }
-
-    private void setupPipes() {
-        UiAutomation ui = mInstrumentation.getUiAutomation();
-        ParcelFileDescriptor[] pipes = ui.executeShellCommandRw(HID_COMMAND);
-
-        mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pipes[1]);
-        closeQuietly(pipes[0]); // hid command is write-only
-    }
-
-    private void writeHidCommands(byte[] bytes) {
-        try {
-            mOutputStream.write(bytes);
-            mOutputStream.flush();
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    // InputManager.InputDeviceListener functions
-    @Override
-    public void onInputDeviceAdded(int deviceId) {
-        mDeviceAddedSignal.countDown();
-    }
-
-    @Override
-    public void onInputDeviceChanged(int deviceId) {
-    }
-
-    @Override
-    public void onInputDeviceRemoved(int deviceId) {
-    }
 }
diff --git a/libs/input/src/com/android/cts/input/HidJsonParser.java b/libs/input/src/com/android/cts/input/HidJsonParser.java
deleted file mode 100644
index 0bc5ea0..0000000
--- a/libs/input/src/com/android/cts/input/HidJsonParser.java
+++ /dev/null
@@ -1,376 +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 com.android.cts.input;
-
-import android.content.Context;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-
-/**
- * Parse json resource file that contains the test commands for HidDevice
- *
- * For files containing reports and input events, each entry should be in the following format:
- * <code>
- * {"name": "test case name",
- *  "reports": reports,
- *  "events": input_events
- * }
- * </code>
- *
- * {@code reports} - an array of strings that contain hex arrays.
- * {@code input_events} - an array of dicts in the following format:
- * <code>
- * {"action": "down|move|up", "axes": {"axis_x": x, "axis_y": y}, "keycode": "button_a"}
- * </code>
- * {@code "axes"} should only be defined for motion events, and {@code "keycode"} for key events.
- * Timestamps will not be checked.
-
- * Example:
- * <code>
- * [{ "name": "press button A",
- *    "reports": ["report1",
- *                "report2",
- *                "report3"
- *               ],
- *    "events": [{"action": "down", "axes": {"axis_y": 0.5, "axis_x": 0.1}},
- *               {"action": "move", "axes": {"axis_y": 0.0, "axis_x": 0.0}}
- *              ]
- *  },
- *  ... more tests like that
- * ]
- * </code>
- */
-public class HidJsonParser {
-    private static final String TAG = "JsonParser";
-
-    private Context mContext;
-
-    public HidJsonParser(Context context) {
-        mContext = context;
-    }
-
-    /**
-     * Convenience function to create JSONArray from resource.
-     * The resource specified should contain JSON array as the top-level structure.
-     *
-     * @param resourceId The resourceId that contains the json data (typically inside R.raw)
-     */
-    private JSONArray getJsonArrayFromResource(int resourceId) {
-        String data = readRawResource(resourceId);
-        try {
-            return new JSONArray(data);
-        } catch (JSONException e) {
-            throw new RuntimeException(
-                    "Could not parse resource " + resourceId + ", received: " + data);
-        }
-    }
-
-    /**
-     * Convenience function to read in an entire file as a String.
-     *
-     * @param id resourceId of the file
-     * @return contents of the raw resource file as a String
-     */
-    private String readRawResource(int id) {
-        InputStream inputStream = mContext.getResources().openRawResource(id);
-        try {
-            return readFully(inputStream);
-        } catch (IOException e) {
-            throw new RuntimeException("Could not read resource id " + id);
-        }
-    }
-
-    /**
-     * Read register command from raw resource.
-     *
-     * @param resourceId the raw resource id that contains the command
-     * @return the command to register device that can be passed to HidDevice constructor
-     */
-    public String readRegisterCommand(int resourceId) {
-        return readRawResource(resourceId);
-    }
-
-    /**
-     * Read entire input stream until no data remains.
-     *
-     * @param inputStream
-     * @return content of the input stream
-     * @throws IOException
-     */
-    private String readFully(InputStream inputStream) throws IOException {
-        OutputStream baos = new ByteArrayOutputStream();
-        byte[] buffer = new byte[1024];
-        int read = inputStream.read(buffer);
-        while (read >= 0) {
-            baos.write(buffer, 0, read);
-            read = inputStream.read(buffer);
-        }
-        return baos.toString();
-    }
-
-    /**
-     * Extract the device id from the raw resource file. This is needed in order to register
-     * a HidDevice.
-     *
-     * @param resourceId resorce file that contains the register command.
-     * @return hid device id
-     */
-    public int readDeviceId(int resourceId) {
-        try {
-            JSONObject json = new JSONObject(readRawResource(resourceId));
-            return json.getInt("id");
-        } catch (JSONException e) {
-            throw new RuntimeException("Could not read device id from resource " + resourceId);
-        }
-    }
-
-    /**
-     * Read json resource, and return a {@code List} of HidTestData, which contains
-     * the name of each test, along with the HID reports and the expected input events.
-     */
-    public List<HidTestData> getTestData(int resourceId) {
-        JSONArray json = getJsonArrayFromResource(resourceId);
-        List<HidTestData> tests = new ArrayList<HidTestData>();
-        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
-            HidTestData testData = new HidTestData();
-
-            try {
-                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
-                testData.name = testcaseEntry.getString("name");
-                JSONArray reports = testcaseEntry.getJSONArray("reports");
-
-                for (int i = 0; i < reports.length(); i++) {
-                    String report = reports.getString(i);
-                    testData.reports.add(report);
-                }
-
-                final int source = sourceFromString(testcaseEntry.optString("source"));
-
-                JSONArray events = testcaseEntry.getJSONArray("events");
-                for (int i = 0; i < events.length(); i++) {
-                    JSONObject entry = events.getJSONObject(i);
-
-                    InputEvent event;
-                    if (entry.has("keycode")) {
-                        event = parseKeyEvent(source, entry);
-                    } else if (entry.has("axes")) {
-                        event = parseMotionEvent(source, entry);
-                    } else {
-                        throw new RuntimeException(
-                                "Input event is not specified correctly. Received: " + entry);
-                    }
-                    testData.events.add(event);
-                }
-                tests.add(testData);
-            } catch (JSONException e) {
-                throw new RuntimeException("Could not process entry " + testCaseNumber);
-            }
-        }
-        return tests;
-    }
-
-    private KeyEvent parseKeyEvent(int source, JSONObject entry) throws JSONException {
-        int action = keyActionFromString(entry.getString("action"));
-        int keyCode = KeyEvent.keyCodeFromString(entry.getString("keycode"));
-        int metaState = metaStateFromString(entry.optString("metaState"));
-        // We will only check select fields of the KeyEvent. Times are not checked.
-        return new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode,
-                /* repeat */ 0, metaState, /* deviceId */ 0, /* scanCode */ 0,
-                /* flags */ 0, source);
-    }
-
-    private MotionEvent parseMotionEvent(int source, JSONObject entry) throws JSONException {
-        MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
-        properties[0] = new MotionEvent.PointerProperties();
-        properties[0].id = 0;
-        properties[0].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
-
-        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[1];
-        coords[0] = new MotionEvent.PointerCoords();
-
-        JSONObject axes = entry.getJSONObject("axes");
-        Iterator<String> keys = axes.keys();
-        while (keys.hasNext()) {
-            String axis = keys.next();
-            float value = (float) axes.getDouble(axis);
-            coords[0].setAxisValue(MotionEvent.axisFromString(axis), value);
-        }
-
-        int buttonState = 0;
-        JSONArray buttons = entry.optJSONArray("buttonState");
-        if (buttons != null) {
-            for (int i = 0; i < buttons.length(); ++i) {
-                buttonState |= motionButtonFromString(buttons.getString(i));
-            }
-        }
-
-        int action = motionActionFromString(entry.getString("action"));
-        // Only care about axes, action and source here. Times are not checked.
-        return MotionEvent.obtain(/* downTime */ 0, /* eventTime */ 0, action,
-                /* pointercount */ 1, properties, coords, 0, buttonState, 0f, 0f,
-                0, 0, source, 0);
-    }
-
-    private static int keyActionFromString(String action) {
-        switch (action.toUpperCase()) {
-            case "DOWN":
-                return KeyEvent.ACTION_DOWN;
-            case "UP":
-                return KeyEvent.ACTION_UP;
-        }
-        throw new RuntimeException("Unknown action specified: " + action);
-    }
-
-    private static int metaStateFromString(String metaStateString) {
-        int metaState = 0;
-        if (metaStateString.isEmpty()) {
-            return metaState;
-        }
-        final String[] metaKeys = metaStateString.split("\\|");
-        for (final String metaKeyString : metaKeys) {
-            final String trimmedKeyString = metaKeyString.trim();
-            switch (trimmedKeyString.toUpperCase()) {
-                case "SHIFT_LEFT":
-                    metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON;
-                    break;
-                case "SHIFT_RIGHT":
-                    metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_RIGHT_ON;
-                    break;
-                case "CTRL_LEFT":
-                    metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON;
-                    break;
-                case "CTRL_RIGHT":
-                    metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_RIGHT_ON;
-                    break;
-                case "ALT_LEFT":
-                    metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON;
-                    break;
-                case "ALT_RIGHT":
-                    metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON;
-                    break;
-                case "META_LEFT":
-                    metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_LEFT_ON;
-                    break;
-                case "META_RIGHT":
-                    metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_RIGHT_ON;
-                    break;
-                case "CAPS_LOCK":
-                    metaState |= KeyEvent.META_CAPS_LOCK_ON;
-                    break;
-                case "NUM_LOCK":
-                    metaState |= KeyEvent.META_NUM_LOCK_ON;
-                    break;
-                case "SCROLL_LOCK":
-                    metaState |= KeyEvent.META_SCROLL_LOCK_ON;
-                    break;
-                default:
-                    throw new RuntimeException("Unknown meta state chunk: " + trimmedKeyString
-                            + " in meta state string: " + metaStateString);
-            }
-        }
-        return metaState;
-    }
-
-    private static int motionActionFromString(String action) {
-        switch (action.toUpperCase()) {
-            case "DOWN":
-                return MotionEvent.ACTION_DOWN;
-            case "MOVE":
-                return MotionEvent.ACTION_MOVE;
-            case "UP":
-                return MotionEvent.ACTION_UP;
-            case "BUTTON_PRESS":
-                return MotionEvent.ACTION_BUTTON_PRESS;
-            case "BUTTON_RELEASE":
-                return MotionEvent.ACTION_BUTTON_RELEASE;
-            case "HOVER_ENTER":
-                return MotionEvent.ACTION_HOVER_ENTER;
-            case "HOVER_MOVE":
-                return MotionEvent.ACTION_HOVER_MOVE;
-            case "HOVER_EXIT":
-                return MotionEvent.ACTION_HOVER_EXIT;
-        }
-        throw new RuntimeException("Unknown action specified: " + action);
-    }
-
-    private static int sourceFromString(String sourceString) {
-        if (sourceString.isEmpty()) {
-            return InputDevice.SOURCE_UNKNOWN;
-        }
-        int source = 0;
-        final String[] sourceEntries = sourceString.split("\\|");
-        for (final String sourceEntry : sourceEntries) {
-            final String trimmedSourceEntry = sourceEntry.trim();
-            switch (trimmedSourceEntry.toUpperCase()) {
-                case "MOUSE_RELATIVE":
-                    source |= InputDevice.SOURCE_MOUSE_RELATIVE;
-                    break;
-                case "JOYSTICK":
-                    source |= InputDevice.SOURCE_JOYSTICK;
-                    break;
-                case "KEYBOARD":
-                    source |= InputDevice.SOURCE_KEYBOARD;
-                    break;
-                case "GAMEPAD":
-                    source |= InputDevice.SOURCE_GAMEPAD;
-                    break;
-                case "DPAD":
-                    source |= InputDevice.SOURCE_DPAD;
-                    break;
-                default:
-                    throw new RuntimeException("Unknown source chunk: " + trimmedSourceEntry
-                            + " in source string: " + sourceString);
-            }
-        }
-        return source;
-    }
-
-    private static int motionButtonFromString(String button) {
-        switch (button.toUpperCase()) {
-            case "BACK":
-                return MotionEvent.BUTTON_BACK;
-            case "FORWARD":
-                return MotionEvent.BUTTON_FORWARD;
-            case "PRIMARY":
-                return MotionEvent.BUTTON_PRIMARY;
-            case "SECONDARY":
-                return MotionEvent.BUTTON_SECONDARY;
-            case "STYLUS_PRIMARY":
-                return MotionEvent.BUTTON_STYLUS_PRIMARY;
-            case "STYLUS_SECONDARY":
-                return MotionEvent.BUTTON_STYLUS_SECONDARY;
-            case "TERTIARY":
-                return MotionEvent.BUTTON_TERTIARY;
-        }
-        throw new RuntimeException("Unknown button specified: " + button);
-    }
-}
diff --git a/libs/input/src/com/android/cts/input/HidResultData.java b/libs/input/src/com/android/cts/input/HidResultData.java
new file mode 100644
index 0000000..5dbd612
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/HidResultData.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.input;
+
+/**
+ * Data class that stores Hid test data result returned from hid command.
+ *
+ */
+public class HidResultData {
+    // event Id of test result
+    public byte eventId;
+
+    // Device Id
+    public int deviceId;
+
+    // report type
+    public byte reportType;
+
+    // report data
+    public byte[] reportData;
+}
diff --git a/libs/input/src/com/android/cts/input/HidVibratorTestData.java b/libs/input/src/com/android/cts/input/HidVibratorTestData.java
new file mode 100644
index 0000000..b67c082
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/HidVibratorTestData.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.input;
+
+import android.util.ArrayMap;
+
+import java.util.List;
+
+/**
+ * Data class that stores HID vibrator test data.
+ */
+public class HidVibratorTestData {
+    // Array of vibrator durations
+    public List<Long> durations;
+
+    // Array of vibrator amplitudes
+    public List<Integer> amplitudes;
+
+    // Index of left FF effect in hid output.
+    public int leftFfIndex;
+
+    // Index of right FF effect in hid output.
+    public int rightFfIndex;
+
+    // Hid output verification check, index and expected data.
+    public ArrayMap<Integer, Integer> verifyMap;
+}
diff --git a/libs/input/src/com/android/cts/input/InputJsonParser.java b/libs/input/src/com/android/cts/input/InputJsonParser.java
new file mode 100644
index 0000000..2353e4e
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/InputJsonParser.java
@@ -0,0 +1,516 @@
+/*
+ * 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 com.android.cts.input;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.util.ArrayMap;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Parse json resource file that contains the test commands for HidDevice
+ *
+ * For files containing reports and input events, each entry should be in the following format:
+ * <code>
+ * {"name": "test case name",
+ *  "reports": reports,
+ *  "events": input_events
+ * }
+ * </code>
+ *
+ * {@code reports} - an array of strings that contain hex arrays.
+ * {@code input_events} - an array of dicts in the following format:
+ * <code>
+ * {"action": "down|move|up", "axes": {"axis_x": x, "axis_y": y}, "keycode": "button_a"}
+ * </code>
+ * {@code "axes"} should only be defined for motion events, and {@code "keycode"} for key events.
+ * Timestamps will not be checked.
+
+ * Example:
+ * <code>
+ * [{ "name": "press button A",
+ *    "reports": ["report1",
+ *                "report2",
+ *                "report3"
+ *               ],
+ *    "events": [{"action": "down", "axes": {"axis_y": 0.5, "axis_x": 0.1}},
+ *               {"action": "move", "axes": {"axis_y": 0.0, "axis_x": 0.0}}
+ *              ]
+ *  },
+ *  ... more tests like that
+ * ]
+ * </code>
+ */
+public class InputJsonParser {
+    private static final String TAG = "InputJsonParser";
+
+    private Context mContext;
+
+    public InputJsonParser(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Convenience function to create JSONArray from resource.
+     * The resource specified should contain JSON array as the top-level structure.
+     *
+     * @param resourceId The resourceId that contains the json data (typically inside R.raw)
+     */
+    private JSONArray getJsonArrayFromResource(int resourceId) {
+        String data = readRawResource(resourceId);
+        try {
+            return new JSONArray(data);
+        } catch (JSONException e) {
+            throw new RuntimeException(
+                    "Could not parse resource " + resourceId + ", received: " + data);
+        }
+    }
+
+    /**
+     * Convenience function to read in an entire file as a String.
+     *
+     * @param id resourceId of the file
+     * @return contents of the raw resource file as a String
+     */
+    private String readRawResource(int id) {
+        InputStream inputStream = mContext.getResources().openRawResource(id);
+        try {
+            return readFully(inputStream);
+        } catch (IOException e) {
+            throw new RuntimeException("Could not read resource id " + id);
+        }
+    }
+
+    /**
+     * Read register command from raw resource.
+     *
+     * @param resourceId the raw resource id that contains the command
+     * @return the command to register device that can be passed to HidDevice constructor
+     */
+    public String readRegisterCommand(int resourceId) {
+        return readRawResource(resourceId);
+    }
+
+    /**
+     * Read entire input stream until no data remains.
+     *
+     * @param inputStream
+     * @return content of the input stream
+     * @throws IOException
+     */
+    private String readFully(InputStream inputStream) throws IOException {
+        OutputStream baos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int read = inputStream.read(buffer);
+        while (read >= 0) {
+            baos.write(buffer, 0, read);
+            read = inputStream.read(buffer);
+        }
+        return baos.toString();
+    }
+
+    /**
+     * Extract the device id from the raw resource file. This is needed in order to register
+     * a HidDevice.
+     *
+     * @param resourceId resource file that contains the register command.
+     * @return hid device id
+     */
+    public int readDeviceId(int resourceId) {
+        try {
+            JSONObject json = new JSONObject(readRawResource(resourceId));
+            return json.getInt("id");
+        } catch (JSONException e) {
+            throw new RuntimeException("Could not read device id from resource " + resourceId);
+        }
+    }
+
+    /**
+     * Extract the Vendor id from the raw resource file.
+     *
+     * @param resourceId resource file that contains the register command.
+     * @return device vendor id
+     */
+    public int readVendorId(int resourceId) {
+        try {
+            JSONObject json = new JSONObject(readRawResource(resourceId));
+            return json.getInt("vid");
+        } catch (JSONException e) {
+            throw new RuntimeException("Could not read vendor id from resource " + resourceId);
+        }
+    }
+
+    /**
+     * Extract the Product id from the raw resource file.
+     *
+     * @param resourceId resource file that contains the register command.
+     * @return device product id
+     */
+    public int readProductId(int resourceId) {
+        try {
+            JSONObject json = new JSONObject(readRawResource(resourceId));
+            return json.getInt("pid");
+        } catch (JSONException e) {
+            throw new RuntimeException("Could not read prduct id from resource " + resourceId);
+        }
+    }
+
+    private InputEvent parseInputEvent(int testCaseNumber, int source, JSONObject entry) {
+        try {
+            InputEvent event;
+            if (entry.has("keycode")) {
+                event = parseKeyEvent(source, entry);
+            } else if (entry.has("axes")) {
+                event = parseMotionEvent(source, entry);
+            } else {
+                throw new RuntimeException(
+                        "Input event is not specified correctly. Received: " + entry);
+            }
+            return event;
+        } catch (JSONException e) {
+            throw new RuntimeException("Could not process entry " + testCaseNumber + " : " + entry);
+        }
+    }
+
+    /**
+     * Read json resource, and return a {@code List} of HidTestData, which contains
+     * the name of each test, along with the HID reports and the expected input events.
+     */
+    public List<HidTestData> getHidTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<HidTestData> tests = new ArrayList<HidTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            HidTestData testData = new HidTestData();
+
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+                testData.name = testcaseEntry.getString("name");
+                JSONArray reports = testcaseEntry.getJSONArray("reports");
+
+                for (int i = 0; i < reports.length(); i++) {
+                    String report = reports.getString(i);
+                    testData.reports.add(report);
+                }
+
+                final int source = sourceFromString(testcaseEntry.optString("source"));
+                JSONArray events = testcaseEntry.getJSONArray("events");
+                for (int i = 0; i < events.length(); i++) {
+                    testData.events.add(parseInputEvent(i, source, events.getJSONObject(i)));
+                }
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber);
+            }
+        }
+        return tests;
+    }
+
+    /**
+     * Read json resource, and return a {@code List} of HidVibratorTestData, which contains
+     * the vibrator FF effect strength data index, and the hid output verification data.
+     */
+    public List<HidVibratorTestData> getHidVibratorTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<HidVibratorTestData> tests = new ArrayList<HidVibratorTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            HidVibratorTestData testData = new HidVibratorTestData();
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+                testData.leftFfIndex = testcaseEntry.getInt("leftFfIndex");
+                testData.rightFfIndex = testcaseEntry.getInt("rightFfIndex");
+
+                JSONArray durationsArray = testcaseEntry.getJSONArray("durations");
+                JSONArray amplitudesArray = testcaseEntry.getJSONArray("amplitudes");
+                assertTrue(durationsArray.length() == amplitudesArray.length());
+                testData.durations = new ArrayList<Long>();
+                testData.amplitudes = new ArrayList<Integer>();
+                for (int i = 0; i < durationsArray.length(); i++) {
+                    testData.durations.add(durationsArray.getLong(i));
+                    testData.amplitudes.add(amplitudesArray.getInt(i));
+                }
+
+                JSONArray outputArray = testcaseEntry.getJSONArray("output");
+                testData.verifyMap = new ArrayMap<Integer, Integer>();
+                for (int i = 0; i < outputArray.length(); i++) {
+                    JSONObject item = outputArray.getJSONObject(i);
+                    int index = item.getInt("index");
+                    int data = item.getInt("data");
+                    testData.verifyMap.put(index, data);
+                }
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber);
+            }
+        }
+        return tests;
+    }
+
+    /**
+     * Read json resource, and return a {@code List} of UinputVibratorTestData, which contains
+     * the vibrator FF effect of durations and amplitudes.
+     */
+    public List<UinputVibratorTestData> getUinputVibratorTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<UinputVibratorTestData> tests = new ArrayList<UinputVibratorTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            UinputVibratorTestData testData = new UinputVibratorTestData();
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+
+                JSONArray durationsArray = testcaseEntry.getJSONArray("durations");
+                JSONArray amplitudesArray = testcaseEntry.getJSONArray("amplitudes");
+                assertTrue(durationsArray.length() == amplitudesArray.length());
+                testData.durations = new ArrayList<Long>();
+                testData.amplitudes = new ArrayList<Integer>();
+                for (int i = 0; i < durationsArray.length(); i++) {
+                    testData.durations.add(durationsArray.getLong(i));
+                    testData.amplitudes.add(amplitudesArray.getInt(i));
+                }
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber);
+            }
+        }
+        return tests;
+    }
+
+    /**
+     * Read json resource, and return a {@code List} of UinputTestData, which contains
+     * the name of each test, along with the uinput injections and the expected input events.
+     */
+    public List<UinputTestData> getUinputTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<UinputTestData> tests = new ArrayList<UinputTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            UinputTestData testData = new UinputTestData();
+
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+                testData.name = testcaseEntry.getString("name");
+                JSONArray reports = testcaseEntry.getJSONArray("injections");
+                for (int i = 0; i < reports.length(); i++) {
+                    String injections = reports.getString(i);
+                    testData.evdevEvents.add(injections);
+                }
+
+                final int source = sourceFromString(testcaseEntry.optString("source"));
+
+                JSONArray events = testcaseEntry.getJSONArray("events");
+                for (int i = 0; i < events.length(); i++) {
+                    testData.events.add(parseInputEvent(i, source, events.getJSONObject(i)));
+                }
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber);
+            }
+        }
+        return tests;
+    }
+
+    private KeyEvent parseKeyEvent(int source, JSONObject entry) throws JSONException {
+        int action = keyActionFromString(entry.getString("action"));
+        int keyCode = KeyEvent.keyCodeFromString(entry.getString("keycode"));
+        int metaState = metaStateFromString(entry.optString("metaState"));
+        // We will only check select fields of the KeyEvent. Times are not checked.
+        return new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode,
+                /* repeat */ 0, metaState, /* deviceId */ 0, /* scanCode */ 0,
+                /* flags */ 0, source);
+    }
+
+    private MotionEvent parseMotionEvent(int source, JSONObject entry) throws JSONException {
+        MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
+        properties[0] = new MotionEvent.PointerProperties();
+        properties[0].id = 0;
+        properties[0].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
+        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[1];
+        coords[0] = new MotionEvent.PointerCoords();
+
+        JSONObject axes = entry.getJSONObject("axes");
+        Iterator<String> keys = axes.keys();
+        while (keys.hasNext()) {
+            String axis = keys.next();
+            float value = (float) axes.getDouble(axis);
+            coords[0].setAxisValue(MotionEvent.axisFromString(axis), value);
+        }
+
+        int buttonState = 0;
+        JSONArray buttons = entry.optJSONArray("buttonState");
+        if (buttons != null) {
+            for (int i = 0; i < buttons.length(); ++i) {
+                buttonState |= motionButtonFromString(buttons.getString(i));
+            }
+        }
+
+        int action = motionActionFromString(entry.getString("action"));
+        // Only care about axes, action and source here. Times are not checked.
+        return MotionEvent.obtain(/* downTime */ 0, /* eventTime */ 0, action,
+                /* pointercount */ 1, properties, coords, 0, buttonState, 0f, 0f,
+                0, 0, source, 0);
+    }
+
+    private static int keyActionFromString(String action) {
+        switch (action.toUpperCase()) {
+            case "DOWN":
+                return KeyEvent.ACTION_DOWN;
+            case "UP":
+                return KeyEvent.ACTION_UP;
+        }
+        throw new RuntimeException("Unknown action specified: " + action);
+    }
+
+    private static int metaStateFromString(String metaStateString) {
+        int metaState = 0;
+        if (metaStateString.isEmpty()) {
+            return metaState;
+        }
+        final String[] metaKeys = metaStateString.split("\\|");
+        for (final String metaKeyString : metaKeys) {
+            final String trimmedKeyString = metaKeyString.trim();
+            switch (trimmedKeyString.toUpperCase()) {
+                case "SHIFT_LEFT":
+                    metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON;
+                    break;
+                case "SHIFT_RIGHT":
+                    metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_RIGHT_ON;
+                    break;
+                case "CTRL_LEFT":
+                    metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON;
+                    break;
+                case "CTRL_RIGHT":
+                    metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_RIGHT_ON;
+                    break;
+                case "ALT_LEFT":
+                    metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON;
+                    break;
+                case "ALT_RIGHT":
+                    metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON;
+                    break;
+                case "META_LEFT":
+                    metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_LEFT_ON;
+                    break;
+                case "META_RIGHT":
+                    metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_RIGHT_ON;
+                    break;
+                case "CAPS_LOCK":
+                    metaState |= KeyEvent.META_CAPS_LOCK_ON;
+                    break;
+                case "NUM_LOCK":
+                    metaState |= KeyEvent.META_NUM_LOCK_ON;
+                    break;
+                case "SCROLL_LOCK":
+                    metaState |= KeyEvent.META_SCROLL_LOCK_ON;
+                    break;
+                default:
+                    throw new RuntimeException("Unknown meta state chunk: " + trimmedKeyString
+                            + " in meta state string: " + metaStateString);
+            }
+        }
+        return metaState;
+    }
+
+    private static int motionActionFromString(String action) {
+        switch (action.toUpperCase()) {
+            case "DOWN":
+                return MotionEvent.ACTION_DOWN;
+            case "MOVE":
+                return MotionEvent.ACTION_MOVE;
+            case "UP":
+                return MotionEvent.ACTION_UP;
+            case "BUTTON_PRESS":
+                return MotionEvent.ACTION_BUTTON_PRESS;
+            case "BUTTON_RELEASE":
+                return MotionEvent.ACTION_BUTTON_RELEASE;
+            case "HOVER_ENTER":
+                return MotionEvent.ACTION_HOVER_ENTER;
+            case "HOVER_MOVE":
+                return MotionEvent.ACTION_HOVER_MOVE;
+            case "HOVER_EXIT":
+                return MotionEvent.ACTION_HOVER_EXIT;
+        }
+        throw new RuntimeException("Unknown action specified: " + action);
+    }
+
+    private static int sourceFromString(String sourceString) {
+        if (sourceString.isEmpty()) {
+            return InputDevice.SOURCE_UNKNOWN;
+        }
+        int source = 0;
+        final String[] sourceEntries = sourceString.split("\\|");
+        for (final String sourceEntry : sourceEntries) {
+            final String trimmedSourceEntry = sourceEntry.trim();
+            switch (trimmedSourceEntry.toUpperCase()) {
+                case "MOUSE_RELATIVE":
+                    source |= InputDevice.SOURCE_MOUSE_RELATIVE;
+                    break;
+                case "JOYSTICK":
+                    source |= InputDevice.SOURCE_JOYSTICK;
+                    break;
+                case "KEYBOARD":
+                    source |= InputDevice.SOURCE_KEYBOARD;
+                    break;
+                case "GAMEPAD":
+                    source |= InputDevice.SOURCE_GAMEPAD;
+                    break;
+                case "DPAD":
+                    source |= InputDevice.SOURCE_DPAD;
+                    break;
+                default:
+                    throw new RuntimeException("Unknown source chunk: " + trimmedSourceEntry
+                            + " in source string: " + sourceString);
+            }
+        }
+        return source;
+    }
+
+    private static int motionButtonFromString(String button) {
+        switch (button.toUpperCase()) {
+            case "BACK":
+                return MotionEvent.BUTTON_BACK;
+            case "FORWARD":
+                return MotionEvent.BUTTON_FORWARD;
+            case "PRIMARY":
+                return MotionEvent.BUTTON_PRIMARY;
+            case "SECONDARY":
+                return MotionEvent.BUTTON_SECONDARY;
+            case "STYLUS_PRIMARY":
+                return MotionEvent.BUTTON_STYLUS_PRIMARY;
+            case "STYLUS_SECONDARY":
+                return MotionEvent.BUTTON_STYLUS_SECONDARY;
+            case "TERTIARY":
+                return MotionEvent.BUTTON_TERTIARY;
+        }
+        throw new RuntimeException("Unknown button specified: " + button);
+    }
+}
diff --git a/libs/input/src/com/android/cts/input/UinputDevice.java b/libs/input/src/com/android/cts/input/UinputDevice.java
new file mode 100644
index 0000000..fe6cc8f
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/UinputDevice.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.input;
+
+import android.app.Instrumentation;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a virtual UINPUT device registered through /dev/uinput.
+ */
+public final class UinputDevice extends VirtualInputDevice {
+    private static final String TAG = "UinputDevice";
+    // uinput executable expects "-" argument to read from stdin instead of a file
+    private static final String UINPUT_COMMAND = "uinput -";
+
+    @GuardedBy("mLock")
+    private List<UinputResultData> mResults = new ArrayList<UinputResultData>();
+
+    @Override
+    protected String getShellCommand() {
+        return UINPUT_COMMAND;
+    }
+
+    @Override
+    protected void readResults() {
+        try {
+            mReader.beginObject();
+            UinputResultData result = new UinputResultData();
+            while (mReader.hasNext()) {
+                String fieldName = mReader.nextName();
+                if (fieldName.equals("reason")) {
+                    result.reason = mReader.nextString();
+                }
+                if (fieldName.equals("id")) {
+                    result.deviceId = Integer.decode(mReader.nextString());
+                }
+                if (fieldName.equals("status")) {
+                    result.status = Integer.decode(mReader.nextString());
+                }
+            }
+            mReader.endObject();
+            addResult(result);
+        } catch (IOException ex) {
+            Log.w(TAG, "Exiting JSON Result reader. " + ex);
+        }
+    }
+
+    public UinputDevice(Instrumentation instrumentation, int deviceId, String registerCommand) {
+        super(instrumentation, deviceId, registerCommand);
+    }
+
+    /**
+     * Get uinput command return results as list of UinputResultData
+     *
+     * @return List of UinputResultData results
+     */
+    public synchronized List<UinputResultData> getResults(int deviceId, String reason)
+            throws IOException {
+        List<UinputResultData> results = new ArrayList<UinputResultData>();
+        synchronized (mLock) {
+            for (UinputResultData result : mResults) {
+                if (deviceId == result.deviceId && reason.equals(reason)) {
+                    results.add(result);
+                }
+            }
+        }
+        return results;
+    }
+
+    /**
+     * Add uinput command returned UinputResultData result
+     *
+     * @param result UinputResultData result
+     */
+    public synchronized void addResult(UinputResultData result) {
+        synchronized (mLock) {
+            if (mId == result.deviceId && mResults != null) {
+                mResults.add(result);
+            }
+        }
+    }
+
+    /**
+     * Inject array of uinput events to the device.  The events array should follow the below
+     * format:
+     *
+     * String evdevEvents = "[0x01, 0x0a, 0x01, 0x01, 0x0a, 0x00 ]"
+     * The above string represents an event array of [EV_KEY, KEY_9, DOWN,  EV_KEY, KEY_9, UP]
+     *
+     * @param evdevEvents The uinput events to be injected.  (a JSON-formatted array of hex)
+     */
+    public void injectEvents(String evdevEvents) {
+        JSONObject json = new JSONObject();
+        try {
+            json.put("command", "inject");
+            json.put("id", mId);
+            json.put("events", new JSONArray(evdevEvents));
+        } catch (JSONException e) {
+            throw new RuntimeException("Could not inject events: " + evdevEvents);
+        }
+        writeCommands(json.toString().getBytes());
+    }
+
+}
diff --git a/libs/input/src/com/android/cts/input/UinputResultData.java b/libs/input/src/com/android/cts/input/UinputResultData.java
new file mode 100644
index 0000000..28f4ca5
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/UinputResultData.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.input;
+
+/**
+ * Data class that stores UINPUT test data result returned from uinput command.
+ *
+ */
+public class UinputResultData {
+    // Reason of the test result
+    public String reason;
+
+    // Device Id
+    public int deviceId;
+
+    // Device status
+    public int status;
+}
diff --git a/libs/input/src/com/android/cts/input/UinputTestData.java b/libs/input/src/com/android/cts/input/UinputTestData.java
new file mode 100644
index 0000000..7fbd07d
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/UinputTestData.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.input;
+
+import android.view.InputEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Data class that stores UINPUT test data.
+ *
+ * There need not be a 1:1 mapping from evdevEvents to events.
+ */
+public class UinputTestData {
+    // Name of the test
+    public String name;
+
+    // Uinput events to be injected to /dev/uinput
+    public List<String> evdevEvents = new ArrayList<String>();
+
+    // InputEvent's that are expected to be produced after sending out the evdevEvents.
+    public List<InputEvent> events = new ArrayList<InputEvent>();
+}
diff --git a/libs/input/src/com/android/cts/input/UinputVibratorTestData.java b/libs/input/src/com/android/cts/input/UinputVibratorTestData.java
new file mode 100644
index 0000000..280ef32
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/UinputVibratorTestData.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.input;
+
+import java.util.List;
+
+/**
+ * Data class that stores HID vibrator test data.
+ */
+public class UinputVibratorTestData {
+    // Array of vibrator durations
+    public List<Long> durations;
+
+    // Array of vibrator amplitudes
+    public List<Integer> amplitudes;
+
+}
diff --git a/libs/input/src/com/android/cts/input/VirtualInputDevice.java b/libs/input/src/com/android/cts/input/VirtualInputDevice.java
new file mode 100644
index 0000000..9b70ac7
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/VirtualInputDevice.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.input;
+
+import static android.os.FileUtils.closeQuietly;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.hardware.input.InputManager;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.util.JsonReader;
+import android.util.JsonToken;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Declares a virtual INPUT device registered through /dev/uinput or /dev/hid.
+ */
+public abstract class VirtualInputDevice implements InputManager.InputDeviceListener {
+    private static final String TAG = "VirtualInputDevice";
+    private InputStream mInputStream;
+    private OutputStream mOutputStream;
+    private Instrumentation mInstrumentation;
+    private final Thread mThread;
+    private volatile CountDownLatch mDeviceAddedSignal; // to wait for onInputDeviceAdded signal
+
+    protected final int mId; // initialized from the json file
+    protected JsonReader mReader;
+    protected final Object mLock = new Object();
+
+    /**
+     * To be implemented with device specific shell command to execute.
+     */
+    abstract String getShellCommand();
+
+    /**
+     * To be implemented with device specific result reading function.
+     */
+    abstract void readResults();
+
+    private final class ResultReader implements Runnable {
+        @Override
+        public void run() {
+            try {
+                while (mReader.peek() != JsonToken.END_DOCUMENT) {
+                    readResults();
+                }
+            } catch (IOException ex) {
+                Log.w(TAG, "Exiting JSON Result reader. " + ex);
+            }
+        }
+    }
+
+    public VirtualInputDevice(Instrumentation instrumentation, int deviceId,
+            String registerCommand) {
+        mInstrumentation = instrumentation;
+        setupPipes();
+
+        mInstrumentation.runOnMainSync(new Runnable(){
+            @Override
+            public void run() {
+                InputManager inputManager =
+                        mInstrumentation.getContext().getSystemService(InputManager.class);
+                inputManager.registerInputDeviceListener(VirtualInputDevice.this, null);
+            }
+        });
+
+        mId = deviceId;
+        mThread = new Thread(new ResultReader());
+        mThread.start();
+        registerInputDevice(registerCommand);
+    }
+
+    protected byte[] readData() throws IOException {
+        ArrayList<Integer> data = new ArrayList<Integer>();
+        try {
+            mReader.beginArray();
+            while (mReader.hasNext()) {
+                data.add(Integer.decode(mReader.nextString()));
+            }
+            mReader.endArray();
+        } catch (IllegalStateException | NumberFormatException e) {
+            mReader.endArray();
+            throw new IllegalStateException("Encountered malformed data.", e);
+        }
+        byte[] rawData = new byte[data.size()];
+        for (int i = 0; i < data.size(); i++) {
+            int d = data.get(i);
+            if ((d & 0xFF) != d) {
+                throw new IllegalStateException("Invalid data, all values must be byte-sized");
+            }
+            rawData[i] = (byte) d;
+        }
+        return rawData;
+    }
+
+    /**
+     * Register an input device. May cause a failure if the device added notification
+     * is not received within the timeout period
+     *
+     * @param registerCommand The full json command that specifies how to register this device
+     */
+    private void registerInputDevice(String registerCommand) {
+        mDeviceAddedSignal = new CountDownLatch(1);
+        Log.i(TAG, "registerInputDevice: " + registerCommand);
+        writeCommands(registerCommand.getBytes());
+        try {
+            // Wait for input device added callback.
+            mDeviceAddedSignal.await(20L, TimeUnit.SECONDS);
+            if (mDeviceAddedSignal.getCount() != 0) {
+                throw new RuntimeException("Did not receive device added notification in time");
+            }
+        } catch (InterruptedException ex) {
+            throw new RuntimeException(
+                    "Unexpectedly interrupted while waiting for device added notification.");
+        }
+        // Even though the device has been added, it still may not be ready to process the events
+        // right away. This seems to be a kernel bug.
+        // Add a small delay here to ensure device is "ready".
+        SystemClock.sleep(1000);
+    }
+
+    /**
+     * Add a delay between processing events.
+     *
+     * @param milliSeconds The delay in milliseconds.
+     */
+    public void delay(int milliSeconds) {
+        JSONObject json = new JSONObject();
+        try {
+            json.put("command", "delay");
+            json.put("id", mId);
+            json.put("duration", milliSeconds);
+        } catch (JSONException e) {
+            throw new RuntimeException(
+                    "Could not create JSON object to delay " + milliSeconds + " milliseconds");
+        }
+        writeCommands(json.toString().getBytes());
+    }
+
+    /**
+     * Close the device, which would cause the associated input device to unregister.
+     */
+    public void close() {
+        closeQuietly(mInputStream);
+        closeQuietly(mOutputStream);
+        // mThread should exit when stream is closed.
+    }
+
+    private void setupPipes() {
+        UiAutomation ui = mInstrumentation.getUiAutomation();
+        ParcelFileDescriptor[] pipes = ui.executeShellCommandRw(getShellCommand());
+
+        mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pipes[0]);
+        mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pipes[1]);
+        try {
+            mReader = new JsonReader(new InputStreamReader(mInputStream, "UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        mReader.setLenient(true);
+    }
+
+    protected void writeCommands(byte[] bytes) {
+        try {
+            mOutputStream.write(bytes);
+            mOutputStream.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // InputManager.InputDeviceListener functions
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        mDeviceAddedSignal.countDown();
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+    }
+}
diff --git a/libs/install/Android.bp b/libs/install/Android.bp
index 3d3d350..60c4793 100644
--- a/libs/install/Android.bp
+++ b/libs/install/Android.bp
@@ -65,6 +65,14 @@
 }
 
 android_test_helper_app {
+    name: "TestAppBv3",
+    manifest: "testapp/Bv3.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v3"],
+}
+
+android_test_helper_app {
     name: "TestAppCv1",
     manifest: "testapp/Cv1.xml",
     sdk_version: "current",
@@ -73,6 +81,14 @@
 }
 
 android_test_helper_app {
+    name: "TestAppCv2",
+    manifest: "testapp/Cv2.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v2"],
+}
+
+android_test_helper_app {
     name: "TestAppASplitV1",
     manifest: "testapp/Av1.xml",
     sdk_version: "current",
@@ -106,7 +122,9 @@
         ":TestAppAv3",
         ":TestAppBv1",
         ":TestAppBv2",
+        ":TestAppBv3",
         ":TestAppCv1",
+        ":TestAppCv2",
         ":TestAppACrashingV2",
         ":TestAppASplitV1",
         ":TestAppASplitV2",
@@ -127,5 +145,5 @@
 java_library_host {
     name: "cts-install-lib-host",
     srcs: ["src/**/host/InstallUtilsHost.java"],
-    libs: ["tradefed"],
+    libs: ["tradefed", "cts-shim-host-lib",],
 }
diff --git a/libs/install/src/android/cts/install/lib/host/InstallUtilsHost.java b/libs/install/src/android/cts/install/lib/host/InstallUtilsHost.java
new file mode 100644
index 0000000..4bbc0da
--- /dev/null
+++ b/libs/install/src/android/cts/install/lib/host/InstallUtilsHost.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.install.lib.host;
+
+import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import java.util.Optional;
+
+/**
+ * Utilities to facilitate installation in tests on host side.
+ */
+public class InstallUtilsHost {
+    private static final String TAG = InstallUtilsHost.class.getSimpleName();
+
+    private final BaseHostJUnit4Test mTest;
+
+    public InstallUtilsHost(BaseHostJUnit4Test test) {
+        mTest = test;
+    }
+
+    /**
+     * Return {@code true} if and only if device supports updating apex.
+     */
+    public boolean isApexUpdateSupported() throws Exception {
+        return mTest.getDevice().getBooleanProperty("ro.apex.updatable", false);
+    }
+
+    /**
+     * Return {@code true} if and only if device supports file system checkpoint.
+     */
+    public boolean isCheckpointSupported() throws Exception {
+        CommandResult result = mTest.getDevice().executeShellV2Command("sm supports-checkpoint");
+        assertWithMessage("Failed to check if file system checkpoint is supported : %s",
+                result.getStderr()).that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+        return "true".equals(result.getStdout().trim());
+    }
+
+    /**
+     * Uninstalls a shim apex only if it's latest version is installed on /data partition (i.e.
+     * it has a version higher than {@code 1}).
+     *
+     * <p>This is purely to optimize tests run time. Since uninstalling an apex requires a reboot,
+     * and only a small subset of tests successfully install an apex, this code avoids ~10
+     * unnecessary reboots.
+     */
+    public void uninstallShimApexIfNecessary() throws Exception {
+        if (!isApexUpdateSupported()) {
+            // Device doesn't support updating apex. Nothing to uninstall.
+            return;
+        }
+        final ITestDevice.ApexInfo shimApex = getShimApex().orElseThrow(
+                () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME));
+        if (shimApex.sourceDir.startsWith("/system")) {
+            // System version is active, nothing to uninstall.
+            return;
+        }
+        // Non system version is active, need to uninstall it and reboot the device.
+        Log.i(TAG, "Uninstalling shim apex");
+        final String errorMessage = mTest.getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
+        if (errorMessage != null) {
+            Log.e(TAG, "Failed to uninstall " + SHIM_APEX_PACKAGE_NAME + " : " + errorMessage);
+        } else {
+            mTest.getDevice().reboot();
+            final ITestDevice.ApexInfo shim = getShimApex().orElseThrow(
+                    () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME));
+            assertThat(shim.versionCode).isEqualTo(1L);
+            assertThat(shim.sourceDir).startsWith("/system");
+        }
+    }
+
+    /**
+     * Returns the active shim apex as optional.
+     */
+    public Optional<ITestDevice.ApexInfo> getShimApex() throws DeviceNotAvailableException {
+        return mTest.getDevice().getActiveApexes().stream().filter(
+                apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME)).findAny();
+    }
+}
diff --git a/libs/install/src/com/android/cts/install/lib/Install.java b/libs/install/src/com/android/cts/install/lib/Install.java
index cd18906..9f278a0 100644
--- a/libs/install/src/com/android/cts/install/lib/Install.java
+++ b/libs/install/src/com/android/cts/install/lib/Install.java
@@ -154,17 +154,10 @@
         int sessionId = createSession();
         try (PackageInstaller.Session session =
                      InstallUtils.openPackageInstallerSession(sessionId)) {
-            session.commit(LocalIntentSender.getIntentSender());
-            Intent result = LocalIntentSender.getIntentSenderResult();
-            int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                    PackageInstaller.STATUS_FAILURE);
-            if (status == -1) {
-                throw new AssertionError("PENDING USER ACTION");
-            } else if (status > 0) {
-                String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-                throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
-            }
-
+            LocalIntentSender sender = new LocalIntentSender();
+            session.commit(sender.getIntentSender());
+            Intent result = sender.getResult();
+            InstallUtils.assertStatusSuccess(result);
             if (mIsStaged) {
                 InstallUtils.waitForSessionReady(sessionId);
             }
diff --git a/libs/install/src/com/android/cts/install/lib/InstallUtils.java b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
index 9a8cfbd..f3dbd8e 100644
--- a/libs/install/src/com/android/cts/install/lib/InstallUtils.java
+++ b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
@@ -17,9 +17,11 @@
 package com.android.cts.install.lib;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.fail;
 
+import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -31,6 +33,7 @@
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.SystemClock;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -41,6 +44,7 @@
 import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Utilities to facilitate installation in tests.
@@ -48,25 +52,31 @@
 public class InstallUtils {
     private static final int NUM_MAX_POLLS = 5;
     private static final int POLL_WAIT_TIME_MILLIS = 200;
+    private static final long GET_UIAUTOMATION_TIMEOUT_MS = 60000;
+
+    private static UiAutomation getUiAutomation() {
+        final long start = SystemClock.uptimeMillis();
+        while (SystemClock.uptimeMillis() - start < GET_UIAUTOMATION_TIMEOUT_MS) {
+            UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+            if (ui != null) {
+                return ui;
+            }
+        }
+        throw new AssertionError("Failed to get UiAutomation");
+    }
 
     /**
      * Adopts the given shell permissions.
      */
     public static void adoptShellPermissionIdentity(String... permissions) {
-        InstrumentationRegistry
-                .getInstrumentation()
-                .getUiAutomation()
-                .adoptShellPermissionIdentity(permissions);
+        getUiAutomation().adoptShellPermissionIdentity(permissions);
     }
 
     /**
      * Drops all shell permissions.
      */
     public static void dropShellPermissionIdentity() {
-        InstrumentationRegistry
-                .getInstrumentation()
-                .getUiAutomation()
-                .dropShellPermissionIdentity();
+        getUiAutomation().dropShellPermissionIdentity();
     }
     /**
      * Returns the version of the given package installed on device.
@@ -84,10 +94,9 @@
     }
 
     /**
-     * Waits for the given session to be marked as ready.
-     * Throws an assertion if the session fails.
+     * Waits for the given session to be marked as ready or failed and returns it.
      */
-    public static void waitForSessionReady(int sessionId) {
+    public static PackageInstaller.SessionInfo waitForSession(int sessionId) {
         BlockingQueue<PackageInstaller.SessionInfo> sessionStatus = new LinkedBlockingQueue<>();
         BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
             @Override
@@ -118,18 +127,30 @@
             if (info.isStagedSessionReady() || info.isStagedSessionFailed()) {
                 sessionStatus.put(info);
             }
-
-            info = sessionStatus.take();
+            info = sessionStatus.poll(60, TimeUnit.SECONDS);
             context.unregisterReceiver(sessionUpdatedReceiver);
-            if (info.isStagedSessionFailed()) {
-                throw new AssertionError(info.getStagedSessionErrorMessage());
-            }
+            assertWithMessage("Timed out while waiting for session to get ready/failed")
+                    .that(info).isNotNull();
+            assertThat(info.getSessionId()).isEqualTo(sessionId);
+            return info;
         } catch (InterruptedException e) {
             throw new AssertionError(e);
         }
     }
 
     /**
+     * Waits for the given session to be marked as ready.
+     * Throws an assertion if the session fails.
+     */
+    public static void waitForSessionReady(int sessionId) {
+        PackageInstaller.SessionInfo info = waitForSession(sessionId);
+        // TODO: migrate to PackageInstallerSessionInfoSubject
+        if (info.isStagedSessionFailed()) {
+            throw new AssertionError(info.getStagedSessionErrorMessage());
+        }
+    }
+
+    /**
      * Returns the info for the given package name.
      */
     public static PackageInfo getPackageInfo(String packageName) {
@@ -340,7 +361,28 @@
             }
         }
         return true;
+    }
 
+    /**
+     * Returns the session by session Id, or null if no session is found.
+     */
+    public static PackageInstaller.SessionInfo getStagedSessionInfo(int sessionId) {
+        PackageInstaller packageInstaller = getPackageInstaller();
+        for (PackageInstaller.SessionInfo session : packageInstaller.getStagedSessions()) {
+            if (session.getSessionId() == sessionId) {
+                return session;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Assert that the given staged session is abandoned. The method assumes that the given session
+     * is staged.
+     * @param sessionId of the staged session
+     */
+    public static void assertStagedSessionIsAbandoned(int sessionId) {
+        assertThat(getStagedSessionInfo(sessionId)).isNull();
     }
 
     /**
diff --git a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
index 4560ab1..cd0a6c1 100644
--- a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
+++ b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
@@ -22,7 +22,9 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.PackageInstaller;
+import android.os.SystemClock;
 import android.util.Log;
+import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -35,46 +37,62 @@
  */
 public class LocalIntentSender extends BroadcastReceiver {
     private static final String TAG = "cts.install.lib";
+    private static final String EXTRA_REQUEST_ID = LocalIntentSender.class.getName() + ".ID";
+    // Access to this member must be synchronized because it is used by multiple threads
+    private static final SparseArray<BlockingQueue<Intent>> sResults = new SparseArray<>();
 
-    private static final BlockingQueue<Intent> sIntentSenderResults = new LinkedBlockingQueue<>();
+    // Generate a unique id to ensure each LocalIntentSender gets its own results.
+    private final int mRequestId = (int) SystemClock.elapsedRealtime();
 
     @Override
     public void onReceive(Context context, Intent intent) {
         Log.i(TAG, "Received intent " + prettyPrint(intent));
-        sIntentSenderResults.add(intent);
+        int id = intent.getIntExtra(EXTRA_REQUEST_ID, 0);
+        BlockingQueue<Intent> queue = getQueue(id);
+        // queue will be null if this broadcast comes from the session staged in previous tests
+        if (queue != null) {
+            queue.add(intent);
+        }
     }
 
     /**
      * Get a LocalIntentSender.
      */
-    public static IntentSender getIntentSender() {
+    public IntentSender getIntentSender() {
+        addQueue(mRequestId);
         Context context = InstrumentationRegistry.getContext();
         Intent intent = new Intent(context, LocalIntentSender.class);
-        PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, 0);
+        intent.putExtra(EXTRA_REQUEST_ID, mRequestId);
+        PendingIntent pending = PendingIntent.getBroadcast(context, mRequestId, intent, 0);
         return pending.getIntentSender();
     }
 
     /**
-     * Returns the most recent Intent sent by a LocalIntentSender.
+     * Returns and remove the most early Intent received by this LocalIntentSender.
      */
-    public static Intent getIntentSenderResult() throws InterruptedException {
-        Intent intent = sIntentSenderResults.take();
+    public Intent getResult() throws InterruptedException {
+        Intent intent = getQueue(mRequestId).take();
         Log.i(TAG, "Taking intent " + prettyPrint(intent));
         return intent;
     }
 
     /**
-     * Returns an Intent that targets the given {@code sessionId}, while discarding others.
+     * Returns the most recent Intent sent by a LocalIntentSender.
+     * TODO(b/136260017): To be removed when all callers are cleaned up.
      */
-    public static Intent getIntentSenderResult(int sessionId) throws InterruptedException {
-        while (true) {
-            Intent intent = sIntentSenderResults.take();
-            if (intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1) == sessionId) {
-                Log.i(TAG, "Taking intent " + prettyPrint(intent));
-                return intent;
-            } else {
-                Log.i(TAG, "Discarding intent " + prettyPrint(intent));
-            }
+    public static Intent getIntentSenderResult() throws InterruptedException {
+        return null;
+    }
+
+    private static BlockingQueue<Intent> getQueue(int requestId) {
+        synchronized (sResults) {
+            return sResults.get(requestId);
+        }
+    }
+
+    private static void addQueue(int requestId) {
+        synchronized (sResults) {
+            sResults.append(requestId, new LinkedBlockingQueue<>());
         }
     }
 
@@ -82,11 +100,13 @@
         int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
         int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
                 PackageInstaller.STATUS_FAILURE);
+        int id = intent.getIntExtra(EXTRA_REQUEST_ID, 0);
         String message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
         return String.format("%s: {\n"
+                + "requestId = %d\n"
                 + "sessionId = %d\n"
                 + "status = %d\n"
                 + "message = %s\n"
-                + "}", intent, sessionId, status, message);
+                + "}", intent, id, sessionId, status, message);
     }
 }
diff --git a/libs/install/src/com/android/cts/install/lib/TestApp.java b/libs/install/src/com/android/cts/install/lib/TestApp.java
index 7434040..4ff5b99 100644
--- a/libs/install/src/com/android/cts/install/lib/TestApp.java
+++ b/libs/install/src/com/android/cts/install/lib/TestApp.java
@@ -53,9 +53,13 @@
             "TestAppBv1.apk");
     public static final TestApp B2 = new TestApp("Bv2", B, 2, /*isApex*/false,
             "TestAppBv2.apk");
+    public static final TestApp B3 = new TestApp("Bv3", B, 3, /*isApex*/false,
+            "TestAppBv3.apk");
 
     public static final TestApp C1 = new TestApp("Cv1", C, 1, /*isApex*/false,
             "TestAppCv1.apk");
+    public static final TestApp C2 = new TestApp("Cv2", C, 2, /*isApex*/false,
+            "TestAppCv2.apk");
 
     // Apex collection
     public static final TestApp Apex1 = new TestApp("Apex1", SHIM_APEX_PACKAGE_NAME, 1,
diff --git a/libs/install/src/com/android/cts/install/lib/Uninstall.java b/libs/install/src/com/android/cts/install/lib/Uninstall.java
index 0444130..899bd11 100644
--- a/libs/install/src/com/android/cts/install/lib/Uninstall.java
+++ b/libs/install/src/com/android/cts/install/lib/Uninstall.java
@@ -46,7 +46,8 @@
         Context context = InstrumentationRegistry.getContext();
         PackageManager packageManager = context.getPackageManager();
         PackageInstaller packageInstaller = packageManager.getPackageInstaller();
-        packageInstaller.uninstall(packageName, LocalIntentSender.getIntentSender());
-        InstallUtils.assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+        LocalIntentSender sender = new LocalIntentSender();
+        packageInstaller.uninstall(packageName, sender.getIntentSender());
+        InstallUtils.assertStatusSuccess(sender.getResult());
     }
 }
diff --git a/libs/install/src/com/android/cts/install/lib/host/InstallUtilsHost.java b/libs/install/src/com/android/cts/install/lib/host/InstallUtilsHost.java
deleted file mode 100644
index 59c38ad..0000000
--- a/libs/install/src/com/android/cts/install/lib/host/InstallUtilsHost.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.install.lib.host;
-
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-/**
- * Utilities to facilitate installation in tests on host side.
- */
-public class InstallUtilsHost {
-
-    private BaseHostJUnit4Test mTest;
-
-    public InstallUtilsHost(BaseHostJUnit4Test test) {
-        mTest = test;
-    }
-
-    /**
-     * Return {@code true} if and only if device supports updating apex.
-     */
-    public boolean isApexUpdateSupported() throws Exception {
-        return mTest.getDevice().getBooleanProperty("ro.apex.updatable", false);
-    }
-}
diff --git a/libs/install/testapp/ACrashingV2.xml b/libs/install/testapp/ACrashingV2.xml
index 338a5b9..ec55930 100644
--- a/libs/install/testapp/ACrashingV2.xml
+++ b/libs/install/testapp/ACrashingV2.xml
@@ -15,21 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.A"
-    android:versionCode="2"
-    android:versionName="2.0" >
+     package="com.android.cts.install.lib.testapp.A"
+     android:versionCode="2"
+     android:versionName="2.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App A v2">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-                  android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.CrashingMainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.CrashingMainActivity"
+             android:exported="true">
             <intent-filter>
-              <action android:name="android.intent.action.MAIN" />
+              <action android:name="android.intent.action.MAIN"/>
               <category android:name="android.intent.category.DEFAULT"/>
-              <category android:name="android.intent.category.LAUNCHER" />
+              <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Av1.xml b/libs/install/testapp/Av1.xml
index e9714fc..0d0a392 100644
--- a/libs/install/testapp/Av1.xml
+++ b/libs/install/testapp/Av1.xml
@@ -15,20 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.A"
-    android:versionCode="1"
-    android:versionName="1.0" >
+     package="com.android.cts.install.lib.testapp.A"
+     android:versionCode="1"
+     android:versionName="1.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App A1">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-                  android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Av2.xml b/libs/install/testapp/Av2.xml
index fd8afa0..d92cfd0 100644
--- a/libs/install/testapp/Av2.xml
+++ b/libs/install/testapp/Av2.xml
@@ -15,20 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.A"
-    android:versionCode="2"
-    android:versionName="2.0" >
+     package="com.android.cts.install.lib.testapp.A"
+     android:versionCode="2"
+     android:versionName="2.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App A2">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-            android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Av3.xml b/libs/install/testapp/Av3.xml
index a7839e3..b5826d1 100644
--- a/libs/install/testapp/Av3.xml
+++ b/libs/install/testapp/Av3.xml
@@ -15,20 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.A"
-    android:versionCode="3"
-    android:versionName="3.0" >
+     package="com.android.cts.install.lib.testapp.A"
+     android:versionCode="3"
+     android:versionName="3.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App A3">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-            android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Bv1.xml b/libs/install/testapp/Bv1.xml
index 403e7e2..9c9b9d3 100644
--- a/libs/install/testapp/Bv1.xml
+++ b/libs/install/testapp/Bv1.xml
@@ -15,20 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.B"
-    android:versionCode="1"
-    android:versionName="1.0" >
+     package="com.android.cts.install.lib.testapp.B"
+     android:versionCode="1"
+     android:versionName="1.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App B1">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-            android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Bv2.xml b/libs/install/testapp/Bv2.xml
index f030c3f..a184b0e 100644
--- a/libs/install/testapp/Bv2.xml
+++ b/libs/install/testapp/Bv2.xml
@@ -15,20 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.B"
-    android:versionCode="2"
-    android:versionName="2.0" >
+     package="com.android.cts.install.lib.testapp.B"
+     android:versionCode="2"
+     android:versionName="2.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App B2">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-            android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Bv3.xml b/libs/install/testapp/Bv3.xml
new file mode 100644
index 0000000..61ef4e70
--- /dev/null
+++ b/libs/install/testapp/Bv3.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.install.lib.testapp.B"
+     android:versionCode="3"
+     android:versionName="3.0">
+
+
+    <uses-sdk android:minSdkVersion="19"/>
+
+    <application android:label="Test App B3">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/Cv1.xml b/libs/install/testapp/Cv1.xml
index edb69f9..63ca6dc 100644
--- a/libs/install/testapp/Cv1.xml
+++ b/libs/install/testapp/Cv1.xml
@@ -16,20 +16,21 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.C"
-    android:versionCode="1"
-    android:versionName="1.0" >
+     package="com.android.cts.install.lib.testapp.C"
+     android:versionCode="1"
+     android:versionName="1.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App C1">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-            android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Cv2.xml b/libs/install/testapp/Cv2.xml
new file mode 100644
index 0000000..93e0bfd
--- /dev/null
+++ b/libs/install/testapp/Cv2.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.install.lib.testapp.C"
+     android:versionCode="2"
+     android:versionName="2.0">
+
+
+    <uses-sdk android:minSdkVersion="19"/>
+
+    <application android:label="Test App C2"
+         android:rollbackDataPolicy="wipe">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/RollbackInfoSubject.java b/libs/rollback/src/com/android/cts/rollback/lib/RollbackInfoSubject.java
index 684f0ec..9f912e0 100644
--- a/libs/rollback/src/com/android/cts/rollback/lib/RollbackInfoSubject.java
+++ b/libs/rollback/src/com/android/cts/rollback/lib/RollbackInfoSubject.java
@@ -33,6 +33,8 @@
  * Subject for asserting things about RollbackInfo instances.
  */
 public final class RollbackInfoSubject extends Subject<RollbackInfoSubject, RollbackInfo> {
+    private final RollbackInfo mActual;
+
     /**
      * Asserts something about RollbackInfo.
      */
@@ -57,27 +59,28 @@
 
     private RollbackInfoSubject(FailureMetadata failureMetadata, RollbackInfo subject) {
         super(failureMetadata, subject);
+        mActual = subject;
     }
 
     /**
      * Asserts that the RollbackInfo has given rollbackId.
      */
     public void hasRollbackId(int rollbackId) {
-        check().that(getSubject().getRollbackId()).isEqualTo(rollbackId);
+        check("getRollbackId()").that(mActual.getRollbackId()).isEqualTo(rollbackId);
     }
 
     /**
      * Asserts that the RollbackInfo is for a staged rollback.
      */
     public void isStaged() {
-        check().that(getSubject().isStaged()).isTrue();
+        check("isStaged()").that(mActual.isStaged()).isTrue();
     }
 
     /**
      * Asserts that the RollbackInfo is not for a staged rollback.
      */
     public void isNotStaged() {
-        check().that(getSubject().isStaged()).isFalse();
+        check("isStaged()").that(mActual.isStaged()).isFalse();
     }
 
     /**
@@ -86,10 +89,10 @@
      */
     public void packagesContainsExactly(Rollback... expected) {
         List<Rollback> actualPackages = new ArrayList<>();
-        for (PackageRollbackInfo info : getSubject().getPackages()) {
+        for (PackageRollbackInfo info : mActual.getPackages()) {
             actualPackages.add(new Rollback(info));
         }
-        check().that(actualPackages).containsExactly((Object[]) expected);
+        check("actualPackages").that(actualPackages).containsExactly((Object[]) expected);
     }
 
     /**
@@ -102,6 +105,7 @@
             expectedVps.add(cause.getVersionedPackage());
         }
 
-        check().that(getSubject().getCausePackages()).containsExactlyElementsIn(expectedVps);
+        check("getCausePackages()").that(mActual.getCausePackages())
+                .containsExactlyElementsIn(expectedVps);
     }
 }
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
index 4d8a3b9..c37aafc 100644
--- a/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
+++ b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
@@ -126,8 +126,9 @@
         }
 
         RollbackManager rm = getRollbackManager();
-        rm.commitRollback(rollbackId, causes, LocalIntentSender.getIntentSender());
-        Intent result = LocalIntentSender.getIntentSenderResult();
+        LocalIntentSender sender = new LocalIntentSender();
+        rm.commitRollback(rollbackId, causes, sender.getIntentSender());
+        Intent result = sender.getResult();
         int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
                 RollbackManager.STATUS_FAILURE);
         if (status != RollbackManager.STATUS_SUCCESS) {
diff --git a/suite/audio_quality/client/AndroidManifest.xml b/suite/audio_quality/client/AndroidManifest.xml
index 70a6b7e..ad6eca1 100644
--- a/suite/audio_quality/client/AndroidManifest.xml
+++ b/suite/audio_quality/client/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!-- Copyright (C) 2012 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.audiotest" >
-<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+     package="com.android.cts.audiotest">
+<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.RECORD_AUDIO"/>
 
-    <application
-        android:label="@string/app_name" >
-        <activity
-            android:name=".CtsAudioClientActivity"
-            android:label="@string/app_name"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" >
+    <application android:label="@string/app_name">
+        <activity android:name=".CtsAudioClientActivity"
+             android:label="@string/app_name"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
 
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/AlarmManager/Android.bp b/tests/AlarmManager/Android.bp
index ba40a70..0512708 100644
--- a/tests/AlarmManager/Android.bp
+++ b/tests/AlarmManager/Android.bp
@@ -27,6 +27,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     platform_apis: true,
 }
diff --git a/tests/AlarmManager/AndroidTest.xml b/tests/AlarmManager/AndroidTest.xml
index 4369acc..d4bbeae 100644
--- a/tests/AlarmManager/AndroidTest.xml
+++ b/tests/AlarmManager/AndroidTest.xml
@@ -32,4 +32,8 @@
         <option name="runtime-hint" value="1m" />
     </test>
 
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="android.scheduling"/>
+    </object>
 </configuration>
diff --git a/tests/AlarmManager/TEST_MAPPING b/tests/AlarmManager/TEST_MAPPING
new file mode 100644
index 0000000..e80fa11
--- /dev/null
+++ b/tests/AlarmManager/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAlarmManagerTestCases"
+    }
+  ]
+}
diff --git a/tests/AlarmManager/app/Android.bp b/tests/AlarmManager/app/Android.bp
index 5a10973..29f002c 100644
--- a/tests/AlarmManager/app/Android.bp
+++ b/tests/AlarmManager/app/Android.bp
@@ -19,6 +19,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     srcs: ["src/**/*.java"],
     dex_preopt: {
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
index 83db3f9..2037619 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -33,6 +33,8 @@
 import android.os.BatteryManager;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 import android.util.LongArray;
@@ -42,6 +44,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.AppStandbyUtils;
+import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -83,9 +86,9 @@
 
     private static final long APP_STANDBY_WINDOW = 10_000;
     private static final String[] APP_BUCKET_QUOTA_KEYS = {
-            "standby_working_quota",
-            "standby_frequent_quota",
-            "standby_rare_quota",
+            "standby_quota_working",
+            "standby_quota_frequent",
+            "standby_quota_rare",
     };
     private static final int[] APP_STANDBY_QUOTAS = {
             5,  // Working set
@@ -93,26 +96,6 @@
             1,  // Rare
     };
 
-    // Settings common for all tests
-    private static final String COMMON_SETTINGS;
-
-    static {
-        final StringBuilder settings = new StringBuilder();
-        settings.append("min_futurity=");
-        settings.append(MIN_FUTURITY);
-        settings.append(",allow_while_idle_short_time=");
-        settings.append(ALLOW_WHILE_IDLE_SHORT_TIME);
-        settings.append(",app_standby_window=");
-        settings.append(APP_STANDBY_WINDOW);
-        for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) {
-            settings.append(",");
-            settings.append(APP_BUCKET_QUOTA_KEYS[i]);
-            settings.append("=");
-            settings.append(APP_STANDBY_QUOTAS[i]);
-        }
-        COMMON_SETTINGS = settings.toString();
-    }
-
     // Save the state before running tests to restore it after we finish testing.
     private static boolean sOrigAppStandbyEnabled;
     // Test app's alarm history to help predict when a subsequent alarm is going to get deferred.
@@ -318,10 +301,23 @@
         }
     }
 
-    private void updateAlarmManagerConstants() throws IOException {
-        final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants ");
-        cmd.append(COMMON_SETTINGS);
-        executeAndLog(cmd.toString());
+    private void updateAlarmManagerConstants() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_ALARM_MANAGER, "min_futurity",
+                    String.valueOf(MIN_FUTURITY), /* makeDefault */ false);
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_ALARM_MANAGER, "allow_while_idle_short_time",
+                    String.valueOf(ALLOW_WHILE_IDLE_SHORT_TIME), /* makeDefault */ false);
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_ALARM_MANAGER, "app_standby_window",
+                    String.valueOf(APP_STANDBY_WINDOW), /* makeDefault */ false);
+            for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) {
+                DeviceConfig.setProperty(
+                        DeviceConfig.NAMESPACE_ALARM_MANAGER, APP_BUCKET_QUOTA_KEYS[i],
+                        String.valueOf(APP_STANDBY_QUOTAS[i]), /* makeDefault */ false);
+            }
+        });
     }
 
     private void setPowerWhitelisted(boolean whitelist) throws IOException {
@@ -331,8 +327,10 @@
         executeAndLog(cmd.toString());
     }
 
-    private void deleteAlarmManagerConstants() throws IOException {
-        executeAndLog("settings delete global alarm_manager_constants");
+    private void deleteAlarmManagerConstants() {
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS,
+                        DeviceConfig.NAMESPACE_ALARM_MANAGER));
     }
 
     private void setAppStandbyBucket(String bucket) throws IOException {
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
index 53937c5..5d79bd8 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
@@ -29,6 +29,8 @@
 import android.content.IntentFilter;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 
@@ -36,6 +38,8 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -160,14 +164,21 @@
         Thread.sleep(DEFAULT_WAIT);
     }
 
-    private void updateAlarmManagerConstants() throws IOException {
-        String cmd = "settings put global alarm_manager_constants min_futurity=0,min_interval="
-                + MIN_REPEATING_INTERVAL;
-        mUiDevice.executeShellCommand(cmd);
+    private void updateAlarmManagerConstants() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_ALARM_MANAGER, "min_futurity",
+                    "0", /* makeDefault */ false);
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_ALARM_MANAGER, "min_interval",
+                    String.valueOf(MIN_REPEATING_INTERVAL), /* makeDefault */ false);
+        });
     }
 
-    private void deleteAlarmManagerConstants() throws IOException {
-        mUiDevice.executeShellCommand("settings delete global alarm_manager_constants");
+    private void deleteAlarmManagerConstants() {
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS,
+                        DeviceConfig.NAMESPACE_ALARM_MANAGER));
     }
 
     private void setAppStandbyBucket(String bucket) throws IOException {
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/InstantAppsTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/InstantAppsTests.java
index 6a34088..371d26d 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/InstantAppsTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/InstantAppsTests.java
@@ -23,6 +23,8 @@
 import android.content.Context;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeInstant;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -79,12 +81,16 @@
 
     @After
     public void deleteAlarmManagerSettings() {
-        SystemUtil.runShellCommand("settings delete global alarm_manager_constants");
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS,
+                        DeviceConfig.NAMESPACE_ALARM_MANAGER));
     }
 
     private void updateAlarmManagerSettings() {
-        final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants ");
-        cmd.append("min_futurity=0");
-        SystemUtil.runShellCommand(cmd.toString());
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_ALARM_MANAGER, "min_futurity",
+                    "0", /* makeDefault */ false);
+        });
     }
 }
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
index ed35dbe..7f730bb 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
@@ -26,6 +26,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -92,7 +94,11 @@
         mAlarmPi = PendingIntent.getBroadcast(mContext, 0, alarmIntent, 0);
         final IntentFilter alarmFilter = new IntentFilter(ACTION_ALARM);
         mContext.registerReceiver(mAlarmReceiver, alarmFilter);
-        SystemUtil.runShellCommand("settings put global alarm_manager_constants min_futurity=500");
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+                    DeviceConfig.setProperty(
+                            DeviceConfig.NAMESPACE_ALARM_MANAGER, "min_futurity",
+                            "500", /* makeDefault */ false);
+                });
         BatteryUtils.runDumpsysBatteryUnplug();
         mTestStartRtc = System.currentTimeMillis();
         mTestStartElapsed = SystemClock.elapsedRealtime();
@@ -129,7 +135,9 @@
 
     @After
     public void tearDown() {
-        SystemUtil.runShellCommand("settings delete global alarm_manager_constants");
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS,
+                        DeviceConfig.NAMESPACE_ALARM_MANAGER));
         BatteryUtils.runDumpsysBatteryReset();
         if (mTimeChanged) {
             // Make an attempt at resetting the clock to normal
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
index d6b5af9..b7a9747 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
@@ -24,6 +24,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -103,8 +105,11 @@
     }
 
     private void setMaxAlarmsPerUid(int maxAlarmsPerUid) {
-        SystemUtil.runShellCommand("settings put global alarm_manager_constants max_alarms_per_uid="
-                + maxAlarmsPerUid);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_ALARM_MANAGER, "max_alarms_per_uid",
+                    String.valueOf(maxAlarmsPerUid), /* makeDefault */ false);
+        });
     }
 
     @After
@@ -117,6 +122,8 @@
 
     @After
     public void deleteAlarmManagerConstants() {
-        SystemUtil.runShellCommand("settings delete global alarm_manager_constants");
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS,
+                        DeviceConfig.NAMESPACE_ALARM_MANAGER));
     }
 }
diff --git a/tests/BlobStore/OWNERS b/tests/BlobStore/OWNERS
index 16b25bb..bf870975 100644
--- a/tests/BlobStore/OWNERS
+++ b/tests/BlobStore/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 95221
-include platform/frameworks/base:apex/blobstore/OWNERS
+include platform/frameworks/base:/apex/blobstore/OWNERS
diff --git a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
index c7b47ad..2f33e3f 100644
--- a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
+++ b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
@@ -53,7 +53,7 @@
 import com.android.compatibility.common.util.ThrowingRunnable;
 import com.android.cts.blob.R;
 import com.android.cts.blob.ICommandReceiver;
-import com.android.utils.blob.DummyBlobData;
+import com.android.utils.blob.FakeBlobData;
 import com.android.utils.blob.Utils;
 
 import org.junit.After;
@@ -138,7 +138,7 @@
 
     @Test
     public void testGetCreateSession() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -151,7 +151,7 @@
 
     @Test
     public void testCreateBlobHandle_invalidArguments() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         final BlobHandle handle = blobData.getBlobHandle();
         try {
@@ -187,7 +187,7 @@
 
     @Test
     public void testAbandonSession() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -205,7 +205,7 @@
 
     @Test
     public void testOpenReadWriteSession() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -230,7 +230,7 @@
 
     @Test
     public void testOpenSession_fromAnotherPkg() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -256,7 +256,7 @@
 
     @Test
     public void testOpenSessionAndAbandon() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -283,7 +283,7 @@
 
     @Test
     public void testCloseSession() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -322,7 +322,7 @@
 
     @Test
     public void testAllowPublicAccess() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -343,7 +343,7 @@
 
     @Test
     public void testAllowPublicAccess_abandonedSession() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -366,7 +366,7 @@
 
     @Test
     public void testAllowSameSignatureAccess() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -387,7 +387,7 @@
 
     @Test
     public void testAllowSameSignatureAccess_abandonedSession() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -410,7 +410,7 @@
 
     @Test
     public void testAllowPackageAccess() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -432,7 +432,7 @@
 
     @Test
     public void testAllowPackageAccess_allowMultiple() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -457,7 +457,7 @@
 
     @Test
     public void testAllowPackageAccess_abandonedSession() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -483,7 +483,7 @@
 
     @Test
     public void testPrivateAccess() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         final TestServiceConnection connection1 = bindToHelperService(HELPER_PKG);
         final TestServiceConnection connection2 = bindToHelperService(HELPER_PKG2);
@@ -517,7 +517,7 @@
 
     @Test
     public void testMixedAccessType() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -542,7 +542,7 @@
 
     @Test
     public void testMixedAccessType_fromMultiplePackages() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         final TestServiceConnection connection1 = bindToHelperService(HELPER_PKG);
         final TestServiceConnection connection2 = bindToHelperService(HELPER_PKG2);
@@ -579,7 +579,7 @@
 
     @Test
     public void testSessionCommit() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -607,7 +607,7 @@
 
     @Test
     public void testSessionCommit_incompleteData() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -626,7 +626,7 @@
 
     @Test
     public void testSessionCommit_incorrectData() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -649,7 +649,7 @@
 
     @Test
     public void testRecommitBlob() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
 
         try {
@@ -673,7 +673,7 @@
 
     @Test
     public void testRecommitBlob_fromMultiplePackages() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         final TestServiceConnection connection = bindToHelperService(HELPER_PKG);
         try {
@@ -701,7 +701,7 @@
     public void testSessionCommit_largeBlob() throws Exception {
         final long fileSizeBytes = Math.min(mBlobStoreManager.getRemainingLeaseQuotaBytes(),
                 150 * 1024L * 1024L);
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                 .setFileSize(fileSizeBytes)
                 .build();
         blobData.prepare();
@@ -740,7 +740,7 @@
             completableFutures[i] = CompletableFuture.supplyAsync(() -> {
                 final int minSizeMb = 30;
                 final long fileSizeBytes = (minSizeMb + random.nextInt(minSizeMb)) * 1024L * 1024L;
-                final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+                final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                         .setFileSize(fileSizeBytes)
                         .build();
                 try {
@@ -774,7 +774,7 @@
             completableFutures[i] = CompletableFuture.supplyAsync(() -> {
                 final int minSizeMb = 30;
                 final long fileSizeBytes = (minSizeMb + random.nextInt(minSizeMb)) * 1024L * 1024L;
-                final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+                final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                         .setFileSize(fileSizeBytes)
                         .build();
                 try {
@@ -824,7 +824,7 @@
 
     @Test
     public void testOpenBlob() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -858,7 +858,7 @@
 
     @Test
     public void testAcquireReleaseLease() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -893,8 +893,8 @@
 
     @Test
     public void testAcquireLease_multipleLeases() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
-        final DummyBlobData blobData2 = new DummyBlobData.Builder(mContext)
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
+        final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext)
                 .setRandomSeed(42)
                 .build();
         blobData.prepare();
@@ -926,7 +926,7 @@
 
     @Test
     public void testAcquireLease_leaseExpired() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
         try {
@@ -947,7 +947,7 @@
     public void testAcquireRelease_deleteAfterDelay() throws Exception {
         final long waitDurationMs = TimeUnit.SECONDS.toMillis(1);
         runWithKeyValues(() -> {
-            final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
             blobData.prepare();
             try {
                 commitBlob(blobData);
@@ -978,7 +978,7 @@
 
     @Test
     public void testAcquireReleaseLease_invalidArguments() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         try {
             assertThrows(NullPointerException.class, () -> mBlobStoreManager.acquireLease(
@@ -1000,7 +1000,7 @@
 
     @Test
     public void testStorageAttributedToSelf() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         final long partialFileSize = 3373L;
 
@@ -1086,7 +1086,7 @@
 
     @Test
     public void testStorageAttribution_acquireLease() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
 
         final StorageStatsManager storageStatsManager = mContext.getSystemService(
@@ -1164,7 +1164,7 @@
 
     @Test
     public void testStorageAttribution_withExpiredLease() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
 
         final StorageStatsManager storageStatsManager = mContext.getSystemService(
@@ -1227,7 +1227,7 @@
         runWithKeyValues(() -> {
             final LongSparseArray<BlobHandle> blobs = new LongSparseArray<>();
             for (long blobSize : new long[] {20L, 30L, 40L}) {
-                final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+                final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                         .setFileSize(blobSize * Utils.MB_IN_BYTES)
                         .build();
                 blobData.prepare();
@@ -1238,7 +1238,7 @@
                 blobs.put(blobSize, blobData.getBlobHandle());
             }
             final long blobSize = 40L;
-            final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+            final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                     .setFileSize(blobSize * Utils.MB_IN_BYTES)
                     .build();
             blobData.prepare();
@@ -1263,7 +1263,7 @@
         final long totalBytes = Environment.getDataDirectory().getTotalSpace();
         final long limitBytes = 50 * Utils.MB_IN_BYTES;
         runWithKeyValues(() -> {
-            final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+            final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                     .setFileSize(limitBytes + (5 * Utils.MB_IN_BYTES))
                     .build();
             blobData.prepare();
@@ -1281,11 +1281,11 @@
         final long limitBytes = 50 * Utils.MB_IN_BYTES;
         final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
         runWithKeyValues(() -> {
-            final DummyBlobData blobData1 = new DummyBlobData.Builder(mContext)
+            final FakeBlobData blobData1 = new FakeBlobData.Builder(mContext)
                     .setFileSize(40 * Utils.MB_IN_BYTES)
                     .build();
             blobData1.prepare();
-            final DummyBlobData blobData2 = new DummyBlobData.Builder(mContext)
+            final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext)
                     .setFileSize(30 * Utils.MB_IN_BYTES)
                     .build();
             blobData2.prepare();
@@ -1309,7 +1309,7 @@
     public void testRemainingLeaseQuota() throws Exception {
         final long initialRemainingQuota = mBlobStoreManager.getRemainingLeaseQuotaBytes();
         final long blobSize = 100 * Utils.MB_IN_BYTES;
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                 .setFileSize(blobSize)
                 .build();
         blobData.prepare();
@@ -1333,7 +1333,7 @@
     public void testRemainingLeaseQuota_withExpiredLease() throws Exception {
         final long initialRemainingQuota = mBlobStoreManager.getRemainingLeaseQuotaBytes();
         final long blobSize = 100 * Utils.MB_IN_BYTES;
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                 .setFileSize(blobSize)
                 .build();
         blobData.prepare();
@@ -1357,7 +1357,7 @@
     @Test
     public void testAccessExpiredBlob() throws Exception {
         final long expiryDurationMs = TimeUnit.SECONDS.toMillis(6);
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                 .setExpiryDurationMs(expiryDurationMs)
                 .build();
         blobData.prepare();
@@ -1382,7 +1382,7 @@
     @Test
     public void testAccessExpiredBlob_withLeaseAcquired() throws Exception {
         final long expiryDurationMs = TimeUnit.SECONDS.toMillis(6);
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
                 .setExpiryDurationMs(expiryDurationMs)
                 .build();
         blobData.prepare();
@@ -1410,7 +1410,7 @@
         final long leaseExpiryDurationMs = TimeUnit.SECONDS.toMillis(2);
         final long leaseAcquisitionWaitDurationMs = TimeUnit.SECONDS.toMillis(1);
         runWithKeyValues(() -> {
-            final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
             blobData.prepare();
             try {
                 final long blobId = commitBlob(blobData);
@@ -1441,7 +1441,7 @@
 
     @Test
     public void testCommitBlobAfterIdleMaintenance() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
         blobData.prepare();
         final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
         final long partialFileSize = 100L;
@@ -1468,7 +1468,7 @@
     public void testExpiredSessionsDeleted() throws Exception {
         final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
         runWithKeyValues(() -> {
-            final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
             blobData.prepare();
 
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -1487,7 +1487,7 @@
     public void testExpiredSessionsDeleted_withPartialData() throws Exception {
         final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
         runWithKeyValues(() -> {
-            final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
             blobData.prepare();
 
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -1509,9 +1509,9 @@
     @Test
     public void testCreateSession_countLimitExceeded() throws Exception {
         runWithKeyValues(() -> {
-            final DummyBlobData blobData1 = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData1 = new FakeBlobData.Builder(mContext).build();
             blobData1.prepare();
-            final DummyBlobData blobData2 = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext).build();
             blobData2.prepare();
 
             mBlobStoreManager.createSession(blobData1.getBlobHandle());
@@ -1523,9 +1523,9 @@
     @Test
     public void testCommitSession_countLimitExceeded() throws Exception {
         runWithKeyValues(() -> {
-            final DummyBlobData blobData1 = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData1 = new FakeBlobData.Builder(mContext).build();
             blobData1.prepare();
-            final DummyBlobData blobData2 = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext).build();
             blobData2.prepare();
 
             commitBlob(blobData1, null /* accessModifier */, true /* expectSuccess */);
@@ -1536,9 +1536,9 @@
     @Test
     public void testLeaseBlob_countLimitExceeded() throws Exception {
         runWithKeyValues(() -> {
-            final DummyBlobData blobData1 = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData1 = new FakeBlobData.Builder(mContext).build();
             blobData1.prepare();
-            final DummyBlobData blobData2 = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext).build();
             blobData2.prepare();
 
             commitBlob(blobData1);
@@ -1553,11 +1553,11 @@
     @Test
     public void testExpiredLease_countLimitExceeded() throws Exception {
         runWithKeyValues(() -> {
-            final DummyBlobData blobData1 = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData1 = new FakeBlobData.Builder(mContext).build();
             blobData1.prepare();
-            final DummyBlobData blobData2 = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData2 = new FakeBlobData.Builder(mContext).build();
             blobData2.prepare();
-            final DummyBlobData blobData3 = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData3 = new FakeBlobData.Builder(mContext).build();
             blobData3.prepare();
             final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
 
@@ -1583,7 +1583,7 @@
     @Test
     public void testAllowPackageAccess_countLimitExceeded() throws Exception {
         runWithKeyValues(() -> {
-            final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+            final FakeBlobData blobData = new FakeBlobData.Builder(mContext).build();
             blobData.prepare();
 
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
@@ -1698,7 +1698,7 @@
         }
     }
 
-    private void commitAndVerifyBlob(DummyBlobData blobData) throws Exception {
+    private void commitAndVerifyBlob(FakeBlobData blobData) throws Exception {
         commitBlob(blobData);
 
         // Verify that blob can be accessed after committing.
@@ -1708,16 +1708,16 @@
         }
     }
 
-    private long commitBlob(DummyBlobData blobData) throws Exception {
+    private long commitBlob(FakeBlobData blobData) throws Exception {
         return commitBlob(blobData, null);
     }
 
-    private long commitBlob(DummyBlobData blobData,
+    private long commitBlob(FakeBlobData blobData,
             AccessModifier accessModifier) throws Exception {
         return commitBlob(blobData, accessModifier, true /* expectSuccess */);
     }
 
-    private long commitBlob(DummyBlobData blobData,
+    private long commitBlob(FakeBlobData blobData,
             AccessModifier accessModifier, boolean expectSuccess) throws Exception {
         final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
         assertThat(sessionId).isGreaterThan(0L);
@@ -1754,12 +1754,12 @@
         void modify(BlobStoreManager.Session session) throws Exception;
     }
 
-    private void commitBlobFromPkg(DummyBlobData blobData, TestServiceConnection serviceConnection)
+    private void commitBlobFromPkg(FakeBlobData blobData, TestServiceConnection serviceConnection)
             throws Exception {
         commitBlobFromPkg(blobData, ICommandReceiver.FLAG_ACCESS_TYPE_PRIVATE, serviceConnection);
     }
 
-    private void commitBlobFromPkg(DummyBlobData blobData, int accessTypeFlags,
+    private void commitBlobFromPkg(FakeBlobData blobData, int accessTypeFlags,
             TestServiceConnection serviceConnection) throws Exception {
         final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver();
         try (ParcelFileDescriptor pfd = blobData.openForRead()) {
@@ -1779,7 +1779,7 @@
         }
     }
 
-    private void assertPkgCanAccess(DummyBlobData blobData, String pkg) throws Exception {
+    private void assertPkgCanAccess(FakeBlobData blobData, String pkg) throws Exception {
         final TestServiceConnection serviceConnection = bindToHelperService(pkg);
         try {
             assertPkgCanAccess(blobData, serviceConnection);
@@ -1788,7 +1788,7 @@
         }
     }
 
-    private void assertPkgCanAccess(DummyBlobData blobData,
+    private void assertPkgCanAccess(FakeBlobData blobData,
             TestServiceConnection serviceConnection) throws Exception {
         final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver();
         commandReceiver.acquireLease(blobData.getBlobHandle());
@@ -1798,7 +1798,7 @@
         }
     }
 
-    private void assertPkgCannotAccess(DummyBlobData blobData, String pkg) throws Exception {
+    private void assertPkgCannotAccess(FakeBlobData blobData, String pkg) throws Exception {
         final TestServiceConnection serviceConnection = bindToHelperService(pkg);
         try {
             assertPkgCannotAccess(blobData, serviceConnection);
@@ -1807,7 +1807,7 @@
         }
     }
 
-    private void assertPkgCannotAccess(DummyBlobData blobData,
+    private void assertPkgCannotAccess(FakeBlobData blobData,
         TestServiceConnection serviceConnection) throws Exception {
         final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver();
         assertThrows(SecurityException.class,
diff --git a/tests/JobScheduler/Android.bp b/tests/JobScheduler/Android.bp
index e747694..1f5488d 100644
--- a/tests/JobScheduler/Android.bp
+++ b/tests/JobScheduler/Android.bp
@@ -30,6 +30,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     // sdk_version: "current",
     platform_apis: true,
diff --git a/tests/JobScheduler/AndroidTest.xml b/tests/JobScheduler/AndroidTest.xml
index d81ead1..bb3433b 100644
--- a/tests/JobScheduler/AndroidTest.xml
+++ b/tests/JobScheduler/AndroidTest.xml
@@ -34,4 +34,9 @@
         <option name="runtime-hint" value="2m" />
         <option name="isolated-storage" value="false" />
     </test>
+
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="android.scheduling"/>
+    </object>
 </configuration>
diff --git a/tests/JobScheduler/JobTestApp/Android.bp b/tests/JobScheduler/JobTestApp/Android.bp
index 6985b18..f77f784 100644
--- a/tests/JobScheduler/JobTestApp/Android.bp
+++ b/tests/JobScheduler/JobTestApp/Android.bp
@@ -20,6 +20,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "current",
 }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
index 0e69ea7..1722221 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
@@ -31,6 +31,8 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
@@ -121,6 +123,9 @@
         SystemUtil.runShellCommand(getInstrumentation(),
                 "cmd jobscheduler reset-execution-quota -u current "
                         + kJobServiceComponent.getPackageName());
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS,
+                        DeviceConfig.NAMESPACE_JOB_SCHEDULER));
 
         // The super method should be called at the end.
         super.tearDown();
@@ -209,4 +214,9 @@
                 + " " + kJobServiceComponent.getPackageName()
                 + " " + jobId);
     }
+
+    static void updateConfiguration(DeviceConfig.Properties properties) {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> DeviceConfig.setProperties(properties));
+    }
 }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ComponentConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ComponentConstraintTest.java
new file mode 100644
index 0000000..1bbc61e
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ComponentConstraintTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.jobscheduler.cts;
+
+import android.app.job.JobInfo;
+import android.content.pm.PackageManager;
+
+/**
+ * Schedules jobs with various component-enabled states.
+ */
+public class ComponentConstraintTest extends BaseJobSchedulerTest {
+    private static final String TAG = "ComponentConstraintTest";
+    /** Unique identifier for the job scheduled by this suite of tests. */
+    private static final int COMPONENT_JOB_ID = ComponentConstraintTest.class.hashCode();
+
+    private JobInfo.Builder mBuilder;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mBuilder = new JobInfo.Builder(COMPONENT_JOB_ID, kJobServiceComponent);
+    }
+
+    public void testScheduleAfterComponentEnabled() throws Exception {
+        setJobServiceEnabled(true);
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(mBuilder.setOverrideDeadline(0).build());
+
+        assertTrue("Job with enabled service didn't fire.", kTestEnvironment.awaitExecution());
+    }
+
+    /*
+        Test intentionally disabled but kept here to acknowledge the case wasn't accidentally
+        forgotten. Historically, JobScheduler has thrown an exception when an app called schedule()
+        with a disabled service. That behavior cannot be changed easily.
+
+        public void testScheduleAfterComponentDisabled() throws Exception {
+            setJobServiceEnabled(false);
+            kTestEnvironment.setExpectedExecutions(0);
+            mJobScheduler.schedule(mBuilder.setOverrideDeadline(0).build());
+
+            assertTrue("Job with disabled service fired.", kTestEnvironment.awaitTimeout());
+        }
+    */
+
+    public void testComponentDisabledAfterSchedule() throws Exception {
+        setJobServiceEnabled(true);
+        kTestEnvironment.setExpectedExecutions(0);
+        mJobScheduler.schedule(mBuilder.setMinimumLatency(1000).setOverrideDeadline(2000).build());
+        setJobServiceEnabled(false);
+
+        assertTrue("Job with disabled service fired.", kTestEnvironment.awaitTimeout());
+    }
+
+    public void testComponentDisabledAndReenabledAfterSchedule() throws Exception {
+        setJobServiceEnabled(true);
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(mBuilder.setMinimumLatency(1000).setOverrideDeadline(2000).build());
+
+        setJobServiceEnabled(false);
+        assertTrue("Job with disabled service fired.", kTestEnvironment.awaitTimeout());
+
+        setJobServiceEnabled(true);
+        assertTrue("Job with enabled service didn't fire.", kTestEnvironment.awaitExecution());
+    }
+
+    private void setJobServiceEnabled(boolean enabled) {
+        final int state = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+        getContext().getPackageManager().setComponentEnabledSetting(
+                kJobServiceComponent, state, PackageManager.DONT_KILL_APP);
+    }
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
index faeb8f9..1fd784b 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
@@ -143,7 +143,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job with unmetered constraint did not fire on WiFi.",
                 kTestEnvironment.awaitExecution());
@@ -164,7 +164,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job with connectivity constraint did not fire on WiFi.",
                 kTestEnvironment.awaitExecution());
@@ -187,7 +187,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job with connectivity constraint did not fire on WiFi.",
                 kTestEnvironment.awaitExecution());
@@ -208,7 +208,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job with connectivity constraint did not fire on mobile.",
                 kTestEnvironment.awaitExecution());
@@ -234,7 +234,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job with connectivity constraint did not fire on mobile.",
                 kTestEnvironment.awaitExecution());
@@ -260,7 +260,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
         assertTrue("Job with metered connectivity constraint did not fire on mobile.",
                 kTestEnvironment.awaitExecution());
     }
@@ -280,7 +280,7 @@
 
         mTestAppInterface.scheduleJob(false, true);
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
         assertTrue("Job with metered connectivity constraint did not fire on mobile.",
                 mTestAppInterface.awaitJobStart(30_000));
 
@@ -313,7 +313,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
         assertTrue("Job with metered connectivity constraint did not fire on mobile.",
                 kTestEnvironment.awaitExecution());
 
@@ -344,7 +344,7 @@
         mJobScheduler.schedule(
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
                         .build());
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job requiring unmetered connectivity still executed on mobile.",
                 kTestEnvironment.awaitTimeout());
@@ -366,7 +366,7 @@
         mJobScheduler.schedule(
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
                         .build());
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job requiring metered connectivity still executed on WiFi.",
                 kTestEnvironment.awaitTimeout());
@@ -393,7 +393,7 @@
         mJobScheduler.schedule(
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED)
                         .build());
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job requiring metered connectivity still executed on WiFi.",
                 kTestEnvironment.awaitTimeout());
@@ -417,7 +417,7 @@
         kTestEnvironment.setExpectedExecutions(0);
         mJobScheduler.schedule(
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR).build());
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job requiring cellular connectivity still executed on WiFi.",
                 kTestEnvironment.awaitTimeout());
@@ -427,15 +427,6 @@
     // Utility methods
     // --------------------------------------------------------------------------------------------
 
-    /** Asks (not forces) JobScheduler to run the job if functional constraints are met. */
-    private void runJob() throws Exception {
-        // Since connectivity is a functional constraint, calling the "run" command without force
-        // will only get the job to run if the constraint is satisfied.
-        SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler run"
-                + " -u " + UserHandle.myUserId()
-                + " " + kJobServiceComponent.getPackageName() + " " + CONNECTIVITY_JOB_ID);
-    }
-
     /**
      * Determine whether the device running these CTS tests should be subject to tests involving
      * mobile data.
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java
index 7177bf5..7aed1b9 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java
@@ -166,10 +166,7 @@
     }
 
     private boolean isCarModeSupported() {
-        // TVs don't support car mode.
-        return !getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_LEANBACK_ONLY)
-                && !getContext().getSystemService(UiModeManager.class).isUiModeLocked();
+        return !getContext().getSystemService(UiModeManager.class).isUiModeLocked();
     }
 
     /**
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
new file mode 100644
index 0000000..819ee53
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.jobscheduler.cts;
+
+import static android.net.ConnectivityDiagnosticsManager.persistableBundleEquals;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+
+import android.app.job.JobInfo;
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.NetworkRequest;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+
+/**
+ * Tests related to created and reading JobInfo objects.
+ */
+public class JobInfoTest extends BaseJobSchedulerTest {
+    private static final int JOB_ID = JobInfoTest.class.hashCode();
+
+    public void testBackoffCriteria() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setBackoffCriteria(12345, JobInfo.BACKOFF_POLICY_LINEAR)
+                .build();
+        assertEquals(12345, ji.getInitialBackoffMillis());
+        assertEquals(JobInfo.BACKOFF_POLICY_LINEAR, ji.getBackoffPolicy());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setBackoffCriteria(54321, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
+                .build();
+        assertEquals(54321, ji.getInitialBackoffMillis());
+        assertEquals(JobInfo.BACKOFF_POLICY_EXPONENTIAL, ji.getBackoffPolicy());
+    }
+
+    public void testBatteryNotLow() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresBatteryNotLow(true)
+                .build();
+        assertTrue(ji.isRequireBatteryNotLow());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresBatteryNotLow(false)
+                .build();
+        assertFalse(ji.isRequireBatteryNotLow());
+    }
+
+    public void testCharging() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresCharging(true)
+                .build();
+        assertTrue(ji.isRequireCharging());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresCharging(false)
+                .build();
+        assertFalse(ji.isRequireCharging());
+    }
+
+    public void testClipData() {
+        final ClipData clipData = ClipData.newPlainText("test", "testText");
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setClipData(clipData, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                .build();
+        assertEquals(clipData, ji.getClipData());
+        assertEquals(Intent.FLAG_GRANT_READ_URI_PERMISSION, ji.getClipGrantFlags());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setClipData(null, 0)
+                .build();
+        assertNull(ji.getClipData());
+        assertEquals(0, ji.getClipGrantFlags());
+    }
+
+    public void testDeviceIdle() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresDeviceIdle(true)
+                .build();
+        assertTrue(ji.isRequireDeviceIdle());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresDeviceIdle(false)
+                .build();
+        assertFalse(ji.isRequireDeviceIdle());
+    }
+
+    public void testEstimatedNetworkBytes() {
+        try {
+            new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                    .setEstimatedNetworkBytes(500, 1000)
+                    .build();
+            fail("Successfully built a JobInfo specifying estimated network bytes without "
+                    + "requesting network");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .setEstimatedNetworkBytes(500, 1000)
+                .build();
+        assertEquals(500, ji.getEstimatedNetworkDownloadBytes());
+        assertEquals(1000, ji.getEstimatedNetworkUploadBytes());
+    }
+
+    public void testExtras() {
+        final PersistableBundle pb = new PersistableBundle();
+        pb.putInt("random_key", 42);
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPersisted(true)
+                .setExtras(pb)
+                .build();
+        assertTrue(persistableBundleEquals(pb, ji.getExtras()));
+    }
+
+    public void testImportantWhileForeground() {
+        // Assert the default value is false
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .build();
+        assertFalse(ji.isImportantWhileForeground());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setImportantWhileForeground(true)
+                .build();
+        assertTrue(ji.isImportantWhileForeground());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setImportantWhileForeground(false)
+                .build();
+        assertFalse(ji.isImportantWhileForeground());
+    }
+
+    public void testMinimumLatency() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setMinimumLatency(1337)
+                .build();
+        assertEquals(1337, ji.getMinLatencyMillis());
+    }
+
+    public void testOverrideDeadline() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setOverrideDeadline(7357)
+                .build();
+        // ...why are the set/get methods named differently?? >.>
+        assertEquals(7357, ji.getMaxExecutionDelayMillis());
+    }
+
+    public void testPeriodic() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPeriodic(60 * 60 * 1000L)
+                .build();
+        assertTrue(ji.isPeriodic());
+        assertEquals(60 * 60 * 1000L, ji.getIntervalMillis());
+        assertEquals(60 * 60 * 1000L, ji.getFlexMillis());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPeriodic(120 * 60 * 1000L, 20 * 60 * 1000L)
+                .build();
+        assertTrue(ji.isPeriodic());
+        assertEquals(120 * 60 * 1000L, ji.getIntervalMillis());
+        assertEquals(20 * 60 * 1000L, ji.getFlexMillis());
+    }
+
+    public void testPersisted() {
+        // Assert the default value is false
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .build();
+        assertFalse(ji.isPersisted());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPersisted(true)
+                .build();
+        assertTrue(ji.isPersisted());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPersisted(false)
+                .build();
+        assertFalse(ji.isPersisted());
+    }
+
+    public void testPrefetch() {
+        // Assert the default value is false
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .build();
+        assertFalse(ji.isPrefetch());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPrefetch(true)
+                .build();
+        assertTrue(ji.isPrefetch());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPrefetch(false)
+                .build();
+        assertFalse(ji.isPrefetch());
+    }
+
+    public void testRequiredNetwork() {
+        final NetworkRequest nr = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_VALIDATED)
+                .build();
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetwork(nr)
+                .build();
+        assertEquals(nr, ji.getRequiredNetwork());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetwork(null)
+                .build();
+        assertNull(ji.getRequiredNetwork());
+    }
+
+    @SuppressWarnings("deprecation")
+    public void testRequiredNetworkType() {
+        // Assert the default value is NONE
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_ANY, ji.getNetworkType());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_UNMETERED, ji.getNetworkType());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_NOT_ROAMING, ji.getNetworkType());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_CELLULAR, ji.getNetworkType());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+    }
+
+    public void testStorageNotLow() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresStorageNotLow(true)
+                .build();
+        assertTrue(ji.isRequireStorageNotLow());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresStorageNotLow(false)
+                .build();
+        assertFalse(ji.isRequireStorageNotLow());
+    }
+
+    public void testTransientExtras() {
+        final Bundle b = new Bundle();
+        b.putBoolean("random_bool", true);
+        try {
+            new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                    .setPersisted(true)
+                    .setTransientExtras(b)
+                    .build();
+            fail("Successfully built a persisted JobInfo object with transient extras");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setTransientExtras(b)
+                .build();
+        assertEquals(b.size(), ji.getTransientExtras().size());
+        for (String key : b.keySet()) {
+            assertEquals(b.get(key), ji.getTransientExtras().get(key));
+        }
+    }
+
+    public void testTriggerContentMaxDelay() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setTriggerContentMaxDelay(1337)
+                .build();
+        assertEquals(1337, ji.getTriggerContentMaxDelay());
+    }
+
+    public void testTriggerContentUpdateDelay() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setTriggerContentUpdateDelay(1337)
+                .build();
+        assertEquals(1337, ji.getTriggerContentUpdateDelay());
+    }
+
+    public void testTriggerContentUri() {
+        final Uri u = Uri.parse("content://" + MediaStore.AUTHORITY + "/");
+        final JobInfo.TriggerContentUri tcu = new JobInfo.TriggerContentUri(
+                u, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
+        assertEquals(u, tcu.getUri());
+        assertEquals(JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS, tcu.getFlags());
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .addTriggerContentUri(tcu)
+                .build();
+        assertEquals(1, ji.getTriggerContentUris().length);
+        assertEquals(tcu, ji.getTriggerContentUris()[0]);
+
+        final Uri u2 = Uri.parse("content://" + ContactsContract.AUTHORITY + "/");
+        final JobInfo.TriggerContentUri tcu2 = new JobInfo.TriggerContentUri(u2, 0);
+        assertEquals(u2, tcu2.getUri());
+        assertEquals(0, tcu2.getFlags());
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .addTriggerContentUri(tcu)
+                .addTriggerContentUri(tcu2)
+                .build();
+        assertEquals(2, ji.getTriggerContentUris().length);
+        assertEquals(tcu, ji.getTriggerContentUris()[0]);
+        assertEquals(tcu2, ji.getTriggerContentUris()[1]);
+    }
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
index 44a1f62..74503fa 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
@@ -19,7 +19,7 @@
 import android.annotation.TargetApi;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -31,20 +31,9 @@
     private static final int MIN_SCHEDULE_QUOTA = 250;
     private static final int JOB_ID = JobSchedulingTest.class.hashCode();
 
-    private String originalJobSchedulerConstants;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        originalJobSchedulerConstants = Settings.Global.getString(getContext().getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS);
-    }
-
     @Override
     public void tearDown() throws Exception {
         mJobScheduler.cancel(JOB_ID);
-        Settings.Global.putString(getContext().getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS, originalJobSchedulerConstants);
         SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler reset-schedule-quota");
 
         // The super method should be called at the end.
@@ -70,10 +59,14 @@
      * Test that scheduling fails once an app hits the schedule quota limit.
      */
     public void testFailingScheduleOnQuotaExceeded() {
-        Settings.Global.putString(getContext().getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS,
-                "enable_api_quotas=true,aq_schedule_count=300,aq_schedule_window_ms=300000,"
-                        + "aq_schedule_throw_exception=false,aq_schedule_return_failure=true");
+        updateConfiguration(
+                new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                .setBoolean("enable_api_quotas", true)
+                .setInt("aq_schedule_count", 300)
+                .setLong("aq_schedule_window_ms", 300000)
+                .setBoolean("aq_schedule_throw_exception", false)
+                .setBoolean("aq_schedule_return_failure", true)
+                .build());
 
         JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setMinimumLatency(60 * 60 * 1000L)
@@ -92,10 +85,14 @@
      * Test that scheduling succeeds even after an app hits the schedule quota limit.
      */
     public void testContinuingScheduleOnQuotaExceeded() {
-        Settings.Global.putString(getContext().getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS,
-                "enable_api_quotas=true,aq_schedule_count=300,aq_schedule_window_ms=300000,"
-                        + "aq_schedule_throw_exception=false,aq_schedule_return_failure=false");
+        updateConfiguration(
+                new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setBoolean("enable_api_quotas", true)
+                        .setInt("aq_schedule_count", 300)
+                        .setLong("aq_schedule_window_ms", 300000)
+                        .setBoolean("aq_schedule_throw_exception", false)
+                        .setBoolean("aq_schedule_return_failure", false)
+                        .build());
 
         JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setMinimumLatency(60 * 60 * 1000L)
@@ -112,10 +109,14 @@
      * Test that non-persisted jobs aren't limited by quota.
      */
     public void testNonPersistedJobsNotLimited() {
-        Settings.Global.putString(getContext().getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS,
-                "enable_api_quotas=true,aq_schedule_count=300,aq_schedule_window_ms=60000,"
-                        + "aq_schedule_throw_exception=false,aq_schedule_return_failure=true");
+        updateConfiguration(
+                new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                .setBoolean("enable_api_quotas", true)
+                .setInt("aq_schedule_count", 300)
+                .setLong("aq_schedule_window_ms", 60000)
+                .setBoolean("aq_schedule_throw_exception", false)
+                .setBoolean("aq_schedule_return_failure", true)
+                .build());
 
         JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setMinimumLatency(60 * 60 * 1000L)
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index c449af8..eac8d78 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -16,6 +16,7 @@
 
 package android.jobscheduler.cts;
 
+import static android.jobscheduler.cts.BaseJobSchedulerTest.updateConfiguration;
 import static android.jobscheduler.cts.ConnectivityConstraintTest.ensureSavedWifiNetwork;
 import static android.jobscheduler.cts.ConnectivityConstraintTest.setWifiState;
 import static android.jobscheduler.cts.TestAppInterface.TEST_APP_PACKAGE;
@@ -41,6 +42,7 @@
 import android.os.Temperature;
 import android.os.UserHandle;
 import android.platform.test.annotations.RequiresDevice;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
@@ -52,6 +54,7 @@
 import com.android.compatibility.common.util.AppOpsUtils;
 import com.android.compatibility.common.util.AppStandbyUtils;
 import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.ThermalUtils;
 
 import junit.framework.AssertionFailedError;
@@ -101,7 +104,6 @@
     /** Track whether WiFi was enabled in case we turn it off. */
     private boolean mInitialWiFiState;
     private boolean mInitialAirplaneModeState;
-    private String mInitialJobSchedulerConstants;
     private String mInitialDisplayTimeout;
     private String mInitialRestrictedBucketEnabled;
     private boolean mAutomotiveDevice;
@@ -157,13 +159,12 @@
         mHasWifi = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
         mInitialWiFiState = mWifiManager.isWifiEnabled();
         mInitialAirplaneModeState = isAirplaneModeOn();
-        mInitialJobSchedulerConstants = Settings.Global.getString(mContext.getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS);
         mInitialRestrictedBucketEnabled = Settings.Global.getString(mContext.getContentResolver(),
                 Settings.Global.ENABLE_RESTRICTED_BUCKET);
         // Make sure test jobs can run regardless of bucket.
-        Settings.Global.putString(mContext.getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS, "min_ready_non_active_jobs_count=0");
+        updateConfiguration(
+                new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt("min_ready_non_active_jobs_count", 0).build());
         // Make sure the screen doesn't turn off when the test turns it on.
         mInitialDisplayTimeout =
                 Settings.System.getString(mContext.getContentResolver(), SCREEN_OFF_TIMEOUT);
@@ -527,8 +528,9 @@
                 Log.e(TAG, "Failed to return wifi state to " + mInitialWiFiState, e);
             }
         }
-        Settings.Global.putString(mContext.getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS, mInitialJobSchedulerConstants);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS,
+                        DeviceConfig.NAMESPACE_JOB_SCHEDULER));
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.ENABLE_RESTRICTED_BUCKET, mInitialRestrictedBucketEnabled);
         if (isAirplaneModeOn() != mInitialAirplaneModeState) {
@@ -624,6 +626,7 @@
     private void setScreenState(boolean on) throws Exception {
         if (on) {
             mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            mUiDevice.executeShellCommand("wm dismiss-keyguard");
         } else {
             mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
         }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java
new file mode 100644
index 0000000..e05969e
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.jobscheduler.cts;
+
+import android.app.job.JobInfo;
+import android.app.job.JobWorkItem;
+import android.content.Intent;
+import android.jobscheduler.MockJobService;
+
+import java.util.List;
+
+/**
+ * Tests related to created and reading JobWorkItem objects.
+ */
+public class JobWorkItemTest extends BaseJobSchedulerTest {
+    private static final int JOB_ID = JobWorkItemTest.class.hashCode();
+    private static final Intent TEST_INTENT = new Intent("some.random.action");
+
+    public void testIntentOnlyItem() {
+        JobWorkItem jwi = new JobWorkItem(TEST_INTENT);
+
+        assertEquals(TEST_INTENT, jwi.getIntent());
+        assertEquals(JobInfo.NETWORK_BYTES_UNKNOWN, jwi.getEstimatedNetworkDownloadBytes());
+        assertEquals(JobInfo.NETWORK_BYTES_UNKNOWN, jwi.getEstimatedNetworkUploadBytes());
+        // JobWorkItem hasn't been scheduled yet. Delivery count should be 0.
+        assertEquals(0, jwi.getDeliveryCount());
+    }
+
+    public void testItemWithEstimatedBytes() {
+        JobWorkItem jwi = new JobWorkItem(TEST_INTENT, 10, 20);
+
+        assertEquals(TEST_INTENT, jwi.getIntent());
+        assertEquals(10, jwi.getEstimatedNetworkDownloadBytes());
+        assertEquals(20, jwi.getEstimatedNetworkUploadBytes());
+        // JobWorkItem hasn't been scheduled yet. Delivery count should be 0.
+        assertEquals(0, jwi.getDeliveryCount());
+    }
+
+    public void testDeliveryCountBumped() throws Exception {
+        JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setOverrideDeadline(0)
+                .build();
+        JobWorkItem jwi = new JobWorkItem(TEST_INTENT, 10, 20);
+        // JobWorkItem hasn't been scheduled yet. Delivery count should be 0.
+        assertEquals(0, jwi.getDeliveryCount());
+
+        kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWork(new MockJobService.TestWorkItem[]{
+                new MockJobService.TestWorkItem(TEST_INTENT)});
+        kTestEnvironment.readyToWork();
+        mJobScheduler.enqueue(jobInfo, jwi);
+        runSatisfiedJob(JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        List<JobWorkItem> executedJWIs = kTestEnvironment.getLastReceivedWork();
+        assertEquals(1, executedJWIs.size());
+        assertEquals(1, executedJWIs.get(0).getDeliveryCount());
+    }
+}
diff --git a/tests/JobSchedulerSharedUid/TEST_MAPPING b/tests/JobSchedulerSharedUid/TEST_MAPPING
new file mode 100644
index 0000000..90ff197
--- /dev/null
+++ b/tests/JobSchedulerSharedUid/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsJobSchedulerSharedUidTestCases"
+    }
+  ]
+}
diff --git a/tests/accessibility/AndroidManifest.xml b/tests/accessibility/AndroidManifest.xml
index 4a7348d..bf3b1a8 100644
--- a/tests/accessibility/AndroidManifest.xml
+++ b/tests/accessibility/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2012 The Android Open Source Project
  *
@@ -17,79 +16,83 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.view.accessibility.cts"
-          android:targetSandboxVersion="2">
+     package="android.view.accessibility.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
     <application android:theme="@android:style/Theme.Holo.NoActionBar"
-            android:requestLegacyExternalStorage="true">
+         android:requestLegacyExternalStorage="true">
         <uses-library android:name="android.test.runner"/>
         <service android:name=".SpeakingAccessibilityService"
-                 android:label="@string/title_speaking_accessibility_service"
-                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+             android:label="@string/title_speaking_accessibility_service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accessibilityservice.AccessibilityService"/>
             </intent-filter>
             <meta-data android:name="android.accessibilityservice"
-                       android:resource="@xml/speaking_accessibilityservice" />
+                 android:resource="@xml/speaking_accessibilityservice"/>
         </service>
 
         <service android:name=".VibratingAccessibilityService"
-                 android:label="@string/title_vibrating_accessibility_service"
-                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+             android:label="@string/title_vibrating_accessibility_service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accessibilityservice.AccessibilityService"/>
             </intent-filter>
             <meta-data android:name="android.accessibilityservice"
-                       android:resource="@xml/vibrating_accessibilityservice" />
+                 android:resource="@xml/vibrating_accessibilityservice"/>
         </service>
 
         <service android:name=".SpeakingAndVibratingAccessibilityService"
-                 android:label="@string/title_speaking_and_vibrating_accessibility_service"
-                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+             android:label="@string/title_speaking_and_vibrating_accessibility_service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accessibilityservice.AccessibilityService"/>
             </intent-filter>
             <meta-data android:name="android.accessibilityservice"
-                       android:resource="@xml/speaking_and_vibrating_accessibilityservice" />
+                 android:resource="@xml/speaking_and_vibrating_accessibilityservice"/>
         </service>
 
         <service android:name=".AccessibilityButtonService"
-                 android:label="@string/title_accessibility_button_service"
-                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+             android:label="@string/title_accessibility_button_service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accessibilityservice.AccessibilityService"/>
             </intent-filter>
             <meta-data android:name="android.accessibilityservice"
-                       android:resource="@xml/accessibility_button_service" />
+                 android:resource="@xml/accessibility_button_service"/>
         </service>
 
-        <activity
-            android:label="@string/some_description"
-            android:name=".DummyActivity"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/some_description"
+             android:name=".DummyActivity"
+             android:screenOrientation="locked"/>
 
         <activity android:name=".AccessibilityShortcutTargetActivity"
-                  android:label="@string/shortcut_target_title">
+             android:label="@string/shortcut_target_title"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET" />
+                <category android:name="android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET"/>
             </intent-filter>
             <meta-data android:name="android.accessibilityshortcut.target"
-                       android:resource="@xml/shortcut_target_activity"/>
+                 android:resource="@xml/shortcut_target_activity"/>
         </activity>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.view.accessibility.cts"
-                     android:label="Tests for the accessibility APIs.">
+         android:targetPackage="android.view.accessibility.cts"
+         android:label="Tests for the accessibility APIs.">
         <meta-data android:name="listener"
-                   android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/accessibility/OWNERS b/tests/accessibility/OWNERS
index e54f581..0d2264c 100644
--- a/tests/accessibility/OWNERS
+++ b/tests/accessibility/OWNERS
@@ -1,3 +1,5 @@
 # Bug component: 44214
 pweaver@google.com
 rhedjao@google.com
+qasid@google.com
+ryanlwlin@google.com
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index ff2fed4..29fb23b 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -16,201 +16,180 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.accessibilityservice.cts"
-          android:targetSandboxVersion="2">
+     package="android.accessibilityservice.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
     <application android:theme="@android:style/Theme.Holo.NoActionBar"
-                 android:requestLegacyExternalStorage="true">
+         android:requestLegacyExternalStorage="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:label="@string/accessibility_end_to_end_test_activity"
-            android:name=".activities.AccessibilityEndToEndActivity"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_end_to_end_test_activity"
+             android:name=".activities.AccessibilityEndToEndActivity"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_query_window_test_activity"
-            android:name=".activities.AccessibilityWindowQueryActivity"
-            android:supportsPictureInPicture="true"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_query_window_test_activity"
+             android:name=".activities.AccessibilityWindowQueryActivity"
+             android:supportsPictureInPicture="true"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_view_tree_reporting_test_activity"
-            android:name=".activities.AccessibilityViewTreeReportingActivity"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_view_tree_reporting_test_activity"
+             android:name=".activities.AccessibilityViewTreeReportingActivity"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_focus_and_input_focus_sync_test_activity"
-            android:name=".activities.AccessibilityFocusAndInputFocusSyncActivity"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_focus_and_input_focus_sync_test_activity"
+             android:name=".activities.AccessibilityFocusAndInputFocusSyncActivity"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_text_traversal_test_activity"
-            android:name=".activities.AccessibilityTextTraversalActivity"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_text_traversal_test_activity"
+             android:name=".activities.AccessibilityTextTraversalActivity"
+             android:screenOrientation="locked"/>
 
         <activity android:label="Activity for testing window accessibility reporting"
              android:name=".activities.AccessibilityWindowReportingActivity"
              android:supportsPictureInPicture="true"
              android:screenOrientation="locked"/>
 
-        <activity
-            android:label="Full screen activity for gesture dispatch testing"
-            android:name=".AccessibilityGestureDispatchTest$GestureDispatchActivity"
-            android:screenOrientation="locked" />
+        <activity android:label="Full screen activity for gesture dispatch testing"
+             android:name=".AccessibilityGestureDispatchTest$GestureDispatchActivity"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_soft_keyboard_modes_activity"
-            android:name=".AccessibilitySoftKeyboardModesTest$SoftKeyboardModesActivity" />
+        <activity android:label="@string/accessibility_soft_keyboard_modes_activity"
+             android:name=".AccessibilitySoftKeyboardModesTest$SoftKeyboardModesActivity"/>
 
-        <activity
-            android:label="@string/accessibility_embedded_display_test_parent_activity"
-            android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayParentActivity"
-            android:theme="@android:style/Theme.Dialog"
-            android:screenOrientation="locked" />
+        <activity android:label="@string/accessibility_embedded_display_test_parent_activity"
+             android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayParentActivity"
+             android:theme="@android:style/Theme.Dialog"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_embedded_display_test_activity"
-            android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayActivity"
-            android:screenOrientation="locked" />
+        <activity android:label="@string/accessibility_embedded_display_test_activity"
+             android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayActivity"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_embedded_hierarchy_test_activity"
-            android:name=".AccessibilityEmbeddedHierarchyTest$AccessibilityEmbeddedHierarchyActivity"
-            android:theme="@android:style/Theme.Dialog"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_embedded_hierarchy_test_activity"
+             android:name=".AccessibilityEmbeddedHierarchyTest$AccessibilityEmbeddedHierarchyActivity"
+             android:theme="@android:style/Theme.Dialog"
+             android:screenOrientation="locked"/>
 
-        <service
-            android:name=".StubSystemActionsAccessibilityService"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubSystemActionsAccessibilityService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_system_actions_a11y_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_system_actions_a11y_service"/>
         </service>
 
-        <service
-                android:name=".StubGestureAccessibilityService"
-                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubGestureAccessibilityService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_gesture_dispatch_a11y_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_gesture_dispatch_a11y_service"/>
         </service>
 
-        <service
-                android:name=".GestureDetectionStubAccessibilityService"
-                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".GestureDetectionStubAccessibilityService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
-            <meta-data
-                    android:name="android.accessibilityservice"
-                    android:resource="@xml/stub_gesture_detect_a11y_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_gesture_detect_a11y_service"/>
         </service>
 
-        <service
-                android:name=".TouchExplorationStubAccessibilityService"
-                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".TouchExplorationStubAccessibilityService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
-            <meta-data
-                    android:name="android.accessibilityservice"
-                    android:resource="@xml/stub_touch_exploration_a11y_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_touch_exploration_a11y_service"/>
         </service>
-        <service
-            android:name="android.accessibility.cts.common.InstrumentedAccessibilityService"
-            android:label="@string/title_soft_keyboard_modes_accessibility_service"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name="android.accessibility.cts.common.InstrumentedAccessibilityService"
+             android:label="@string/title_soft_keyboard_modes_accessibility_service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_soft_keyboard_modes_accessibility_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_soft_keyboard_modes_accessibility_service"/>
         </service>
 
-        <service
-            android:name=".StubMagnificationAccessibilityService"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubMagnificationAccessibilityService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_magnification_a11y_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_magnification_a11y_service"/>
         </service>
 
-        <service
-            android:name=".StubFingerprintGestureService"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubFingerprintGestureService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                    android:name="android.accessibilityservice"
-                    android:resource="@xml/stub_fingerprint_gesture_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_fingerprint_gesture_service"/>
         </service>
 
-        <service
-            android:name=".StubAccessibilityButtonService"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubAccessibilityButtonService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_accessibility_button_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_accessibility_button_service"/>
         </service>
 
-        <service
-            android:name=".StubTakeScreenshotService"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubTakeScreenshotService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_take_screenshot_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_take_screenshot_service"/>
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.accessibilityservice.cts"
-        android:label="Tests for the accessibility APIs.">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.accessibilityservice.cts"
+         android:label="Tests for the accessibility APIs.">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
 
     </instrumentation>
 
diff --git a/tests/accessibilityservice/OWNERS b/tests/accessibilityservice/OWNERS
index e54f581..0d2264c 100644
--- a/tests/accessibilityservice/OWNERS
+++ b/tests/accessibilityservice/OWNERS
@@ -1,3 +1,5 @@
 # Bug component: 44214
 pweaver@google.com
 rhedjao@google.com
+qasid@google.com
+ryanlwlin@google.com
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java
index ece2fbf..30b878c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java
@@ -253,7 +253,8 @@
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            mActivityView = new ActivityView(this, null, 0, false, true);
+            mActivityView = new ActivityView.Builder(this)
+                    .setUsePublicVirtualDisplay(true).build();
             setContentView(mActivityView);
         }
 
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 4166213..c50aee2 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -18,6 +18,7 @@
 
 import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithResource;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle;
@@ -699,9 +700,11 @@
         assertFalse(hasTooltipShowing(R.id.buttonWithTooltip));
         assertThat(ACTION_SHOW_TOOLTIP, in(buttonNode.getActionList()));
         assertThat(ACTION_HIDE_TOOLTIP, not(in(buttonNode.getActionList())));
-        sUiAutomation.executeAndWaitForEvent(() -> buttonNode.performAction(
-                ACTION_SHOW_TOOLTIP.getId()),
-                filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
+        sUiAutomation.executeAndWaitForEvent(
+                () -> buttonNode.performAction(ACTION_SHOW_TOOLTIP.getId()),
+                filterForEventTypeWithAction(
+                        AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
+                        ACTION_SHOW_TOOLTIP.getId()),
                 DEFAULT_TIMEOUT_MS);
 
         // The button should now be showing the tooltip, so it should have the option to hide it.
@@ -813,7 +816,9 @@
         // Perform an action and wait for an event
         sUiAutomation.executeAndWaitForEvent(
                 () -> button.performAction(AccessibilityNodeInfo.ACTION_CLICK),
-                filterForEventType(AccessibilityEvent.TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+                filterForEventTypeWithAction(
+                        AccessibilityEvent.TYPE_VIEW_CLICKED, AccessibilityNodeInfo.ACTION_CLICK),
+                DEFAULT_TIMEOUT_MS);
 
         // Make sure the MotionEvent.ACTION_OUTSIDE is received.
         verify(listener, timeout(DEFAULT_TIMEOUT_MS).atLeastOnce()).onTouch(any(View.class),
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
index b7ccc19..1845eea 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -14,8 +14,11 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 
@@ -34,7 +37,6 @@
 import android.platform.test.annotations.Presubmit;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.test.InstrumentationRegistry;
@@ -115,8 +117,8 @@
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(expected.performAction(ACTION_ACCESSIBILITY_FOCUS)),
-                (event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Get the second expected node info.
@@ -151,8 +153,8 @@
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)),
-                (event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Get the node info again.
@@ -174,8 +176,8 @@
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)),
-                (event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Refresh the node info.
@@ -186,8 +188,8 @@
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(rootLinearLayout.performAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS)),
-                (event) -> event.getEventType()
-                        == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, ACTION_CLEAR_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Refresh the node info.
@@ -211,8 +213,8 @@
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(firstEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)),
-                (event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Get the second not focused edit text.
@@ -226,8 +228,8 @@
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(secondEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)),
-                (event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Get the node info again.
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
index 0ca307a..1e71541 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
@@ -317,44 +317,54 @@
         assertTrue(awaitedEvent.getSource().isImportantForAccessibility());
     }
 
-
     @Test
-    public void testHideView_receiveSubtreeEvent() throws Throwable {
+    public void testSetViewInvisible_receiveSubtreeEvent() throws Throwable {
         final View view = mActivity.findViewById(R.id.secondButton);
-        AccessibilityEvent awaitedEvent =
-                sUiAutomation.executeAndWaitForEvent(
-                        () -> mActivity.runOnUiThread(() -> view.setVisibility(View.GONE)),
-                        (event) -> {
-                            boolean isContentChanged = event.getEventType()
-                                    == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
-                            int isSubTree = (event.getContentChangeTypes()
-                                    & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
-                            boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
-                                    mActivity.getPackageName());
-                            return isContentChanged && (isSubTree != 0) && isFromThisPackage;
-                        }, TIMEOUT_ASYNC_PROCESSING);
-        awaitedEvent.recycle();
+        receiveSubtreeEventWhenViewChangesVisibility(view, (View) view.getParentForAccessibility(), View.INVISIBLE);
     }
 
     @Test
-    public void testUnhideView_receiveSubtreeEvent() throws Throwable {
+    public void testSetViewGone_receiveSubtreeEvent() throws Throwable {
+        final View view = mActivity.findViewById(R.id.secondButton);
+        receiveSubtreeEventWhenViewChangesVisibility(view, (View) view.getParentForAccessibility(), View.GONE);
+    }
+
+    @Test
+    public void testSetViewVisible_receiveSubtreeEvent() throws Throwable {
         final View view = mActivity.findViewById(R.id.hiddenButton);
+        receiveSubtreeEventWhenViewChangesVisibility(view, view, View.VISIBLE);
+    }
+
+    private void receiveSubtreeEventWhenViewChangesVisibility(View view, View sendA11yEventParent,
+            int visibility) throws Throwable {
         AccessibilityEvent awaitedEvent =
                 sUiAutomation.executeAndWaitForEvent(
-                        () -> mActivity.runOnUiThread(() -> view.setVisibility(View.VISIBLE)),
+                        () -> {
+                            mActivity.runOnUiThread(() -> view.setVisibility(visibility));
+                        },
                         (event) -> {
                             boolean isContentChanged = event.getEventType()
                                     == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
-                            int isSubTree = (event.getContentChangeTypes()
-                                    & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+                            boolean isSubTree = event.getContentChangeTypes()
+                                    == AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
                             boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
                                     mActivity.getPackageName());
-                            return isContentChanged && (isSubTree != 0) && isFromThisPackage;
+                            boolean isFromThisNode;
+                            if (event.getSource() != null) {
+                                isFromThisNode = TextUtils.equals(
+                                        event.getSource().getViewIdResourceName(),
+                                        sInstrumentation.getTargetContext().getResources()
+                                                .getResourceName(sendA11yEventParent.getId()));
+                            } else {
+                                isFromThisNode = TextUtils.equals(event.getClassName(),
+                                        sendA11yEventParent.getAccessibilityClassName());
+                            }
+                            return isContentChanged && isSubTree && isFromThisPackage
+                                    && isFromThisNode;
                         }, TIMEOUT_ASYNC_PROCESSING);
         awaitedEvent.recycle();
     }
 
-
     private void setGetNonImportantViews(boolean getNonImportantViews) {
         AccessibilityServiceInfo serviceInfo = sUiAutomation.getServiceInfo();
         serviceInfo.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index b32d5b8..5c144d8 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -17,14 +17,17 @@
 package android.accessibilityservice.cts;
 
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangTypesAndWindowId;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangeTypesAndWindowTitle;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.supportsMultiDisplay;
 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
-import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
 import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
+import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
@@ -33,6 +36,7 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOWS_CHANGED;
 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
@@ -63,9 +67,7 @@
 import android.app.UiAutomation;
 import android.app.UiAutomation.AccessibilityEventFilter;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.graphics.Rect;
-import android.os.Bundle;
 import android.platform.test.annotations.AppModeFull;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.SparseArray;
@@ -83,6 +85,8 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.AfterClass;
@@ -215,9 +219,11 @@
                         "android.accessibilityservice.cts:id/button1").get(0);
 
         // Click the button to generate an event
-        AccessibilityEvent event = sUiAutomation.executeAndWaitForEvent(
-                () -> button1.performAction(ACTION_CLICK),
-                filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+        AccessibilityEvent event =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> button1.performAction(ACTION_CLICK),
+                        filterForEventTypeWithAction(TYPE_VIEW_CLICKED, ACTION_CLICK),
+                        DEFAULT_TIMEOUT_MS);
 
         // Make sure the source window cannot be accessed.
         assertNull(event.getSource().getWindow());
@@ -267,9 +273,11 @@
                             "android.accessibilityservice.cts:id/button1").get(0);
 
             // Click the button.
-            AccessibilityEvent event = sUiAutomation.executeAndWaitForEvent(
-                    () -> button1.performAction(ACTION_CLICK),
-                    filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+            AccessibilityEvent event =
+                    sUiAutomation.executeAndWaitForEvent(
+                            () -> button1.performAction(ACTION_CLICK),
+                            filterForEventTypeWithAction(TYPE_VIEW_CLICKED, ACTION_CLICK),
+                            DEFAULT_TIMEOUT_MS);
 
             // Get the source window.
             AccessibilityWindowInfo window = event.getSource().getWindow();
@@ -303,9 +311,11 @@
                             "android.accessibilityservice.cts:id/button1").get(0);
 
             // Click the button.
-            AccessibilityEvent event = sUiAutomation.executeAndWaitForEvent(
-                    () -> button1.performAction(ACTION_CLICK),
-                    filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+            AccessibilityEvent event =
+                    sUiAutomation.executeAndWaitForEvent(
+                            () -> button1.performAction(ACTION_CLICK),
+                            filterForEventTypeWithAction(TYPE_VIEW_CLICKED, ACTION_CLICK),
+                            DEFAULT_TIMEOUT_MS);
 
             // Get the source window.
             AccessibilityWindowInfo window = event.getSource().getWindow();
@@ -316,8 +326,10 @@
                             "android.accessibilityservice.cts:id/button2").get(0);
 
             // Click the second button.
-            sUiAutomation.executeAndWaitForEvent(() -> button2.performAction(ACTION_CLICK),
-                    filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> button2.performAction(ACTION_CLICK),
+                    filterForEventTypeWithAction(TYPE_VIEW_CLICKED, ACTION_CLICK),
+                    DEFAULT_TIMEOUT_MS);
         } finally {
             clearAccessInteractiveWindowsFlag();
         }
@@ -443,9 +455,11 @@
         assertFalse(button.isSelected());
 
         // Perform an action and wait for an event
-        AccessibilityEvent expected = sUiAutomation.executeAndWaitForEvent(
-                () -> button.performAction(ACTION_CLICK),
-                filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+        AccessibilityEvent expected =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> button.performAction(ACTION_CLICK),
+                        filterForEventTypeWithAction(TYPE_VIEW_CLICKED, ACTION_CLICK),
+                        DEFAULT_TIMEOUT_MS);
 
         // Make sure the expected event was received.
         assertNotNull(expected);
@@ -461,9 +475,11 @@
         assertFalse(button.isSelected());
 
         // Perform an action and wait for an event.
-        AccessibilityEvent expected = sUiAutomation.executeAndWaitForEvent(
-                () -> button.performAction(ACTION_LONG_CLICK),
-                filterForEventType(TYPE_VIEW_LONG_CLICKED), DEFAULT_TIMEOUT_MS);
+        AccessibilityEvent expected =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> button.performAction(ACTION_LONG_CLICK),
+                        filterForEventTypeWithAction(TYPE_VIEW_LONG_CLICKED, ACTION_LONG_CLICK),
+                        DEFAULT_TIMEOUT_MS);
 
         // Make sure the expected event was received.
         assertNotNull(expected);
@@ -502,9 +518,11 @@
         assertFalse(button.isSelected());
 
         // focus and wait for the event
-        AccessibilityEvent awaitedEvent = sUiAutomation
-                .executeAndWaitForEvent(() -> button.performAction(ACTION_FOCUS),
-                        filterForEventType(TYPE_VIEW_FOCUSED), DEFAULT_TIMEOUT_MS);
+        AccessibilityEvent awaitedEvent =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> button.performAction(ACTION_FOCUS),
+                        filterForEventTypeWithAction(TYPE_VIEW_FOCUSED, ACTION_FOCUS),
+                        DEFAULT_TIMEOUT_MS);
 
         assertNotNull(awaitedEvent);
 
@@ -575,7 +593,7 @@
     @MediumTest
     @Test
     public void testWindowDockAndUndock_dividerWindowAppearsAndDisappears() throws Exception {
-        if (!ActivityTaskManager.supportsSplitScreenMultiWindow(sInstrumentation.getContext())) {
+        if (!ActivityTaskManager.supportsSplitScreenMultiWindow(mActivity)) {
             // Skipping test: no multi-window support
             return;
         }
@@ -695,6 +713,36 @@
         }
     }
 
+    @Test
+    public void testShowInputMethodDialogWindow_resultIsApplicationType()
+            throws TimeoutException {
+        final WindowManager wm =
+                sInstrumentation.getContext().getSystemService(WindowManager.class);
+        final View view = new View(sInstrumentation.getContext());
+        final String windowTitle = "Input Method Dialog";
+
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                () -> {
+                    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                            WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
+                    params.accessibilityTitle = windowTitle;
+
+                    SystemUtil.runWithShellPermissionIdentity(
+                            () -> wm.addView(view, params),
+                            "android.permission.INTERNAL_SYSTEM_WINDOW");
+                }),
+                filterWindowsChangeTypesAndWindowTitle(sUiAutomation,
+                        WINDOWS_CHANGE_ADDED, windowTitle), DEFAULT_TIMEOUT_MS);
+
+        try {
+            final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+            assertTrue(windows.stream().anyMatch(window -> window.getType()
+                    == AccessibilityWindowInfo.TYPE_APPLICATION));
+        } finally {
+            wm.removeView(view);
+        }
+    }
+
     private AccessibilityWindowInfo findWindow(List<AccessibilityWindowInfo> windows,
             int btnTextRes) {
         return windows.stream()
@@ -848,20 +896,25 @@
 
     private void ensureAccessibilityFocusCleared() {
         try {
-            sUiAutomation.executeAndWaitForEvent(() -> {
-                List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
-                final int windowCount = windows.size();
-                for (int i = 0; i < windowCount; i++) {
-                    AccessibilityWindowInfo window = windows.get(i);
-                    if (window.isAccessibilityFocused()) {
-                        AccessibilityNodeInfo root = window.getRoot();
-                        if (root != null) {
-                            root.performAction(
-                                    AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> {
+                        List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+                        final int windowCount = windows.size();
+                        for (int i = 0; i < windowCount; i++) {
+                            AccessibilityWindowInfo window = windows.get(i);
+                            if (window.isAccessibilityFocused()) {
+                                AccessibilityNodeInfo root = window.getRoot();
+                                if (root != null) {
+                                    root.performAction(
+                                            AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+                                }
+                            }
                         }
-                    }
-                }
-            }, filterForEventType(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED), DEFAULT_TIMEOUT_MS);
+                    },
+                    filterForEventTypeWithAction(
+                            TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+                            ACTION_CLEAR_ACCESSIBILITY_FOCUS),
+                    DEFAULT_TIMEOUT_MS);
         } catch (TimeoutException te) {
             /* ignore */
         }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
index 0fd9477..7f1cd37 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
@@ -52,6 +52,13 @@
         return (both(new AccessibilityEventTypeMatcher(eventType)).and(matchResourceName))::matches;
     }
 
+    public static AccessibilityEventFilter filterForEventTypeWithAction(int eventType, int action) {
+        TypeSafeMatcher<AccessibilityEvent> matchAction =
+                new PropertyMatcher<>(
+                        action, "Action", (event, expect) -> event.getAction() == action);
+        return (both(new AccessibilityEventTypeMatcher(eventType)).and(matchAction))::matches;
+    }
+
     public static AccessibilityEventFilter filterWindowsChangeTypesAndWindowTitle(
             @NonNull UiAutomation uiAutomation, int changeTypes, @NonNull String title) {
         return allOf(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED),
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
index 34b3fc8..08d0936 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
@@ -48,8 +48,10 @@
 
 import com.android.compatibility.common.util.TestUtils;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.TimeoutException;
 import java.util.function.BooleanSupplier;
 import java.util.stream.Collectors;
 
@@ -255,6 +257,7 @@
         final StringBuilder activityPackage = new StringBuilder();
         final Rect bounds = new Rect();
         final StringBuilder activityTitle = new StringBuilder();
+        final StringBuilder timeoutExceptionRecords = new StringBuilder();
         // Make sure we get window events, so we'll know when the window appears
         AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
@@ -265,36 +268,40 @@
             homeScreenOrBust(instrumentation.getContext(), uiAutomation);
         }
 
-        final AccessibilityEvent awaitedEvent = uiAutomation.executeAndWaitForEvent(
-                () -> {
-                    mTempActivity = activityLauncher.launchActivity();
-                    instrumentation.runOnMainSync(() -> {
+        try {
+            final AccessibilityEvent awaitedEvent = uiAutomation.executeAndWaitForEvent(
+                    () -> {
+                        mTempActivity = activityLauncher.launchActivity();
+                        instrumentation.runOnMainSync(() -> {
+                            mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
+                            activityPackage.append(mTempActivity.getPackageName());
+                        });
+                        instrumentation.waitForIdleSync();
+                        activityTitle.append(getActivityTitle(instrumentation, mTempActivity));
+                    },
+                    (event) -> {
+                        final AccessibilityWindowInfo window =
+                                findWindowByTitleAndDisplay(uiAutomation, activityTitle, displayId);
+                        if (window == null) return false;
+                        if (window.getRoot() == null) return false;
+
+                        window.getBoundsInScreen(bounds);
                         mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
-                        activityPackage.append(mTempActivity.getPackageName());
-                    });
-                    instrumentation.waitForIdleSync();
-                    activityTitle.append(getActivityTitle(instrumentation, mTempActivity));
-                },
-                (event) -> {
-                    AccessibilityNodeInfo node = event.getSource();
-                    if (node != null) {
-                        final AccessibilityWindowInfo window = node.getWindow();
-                        if(!TextUtils.equals(activityTitle, window.getTitle())) {
-                            return  false;
-                        }
-                    }
-                    final AccessibilityWindowInfo window =
-                            findWindowByTitleAndDisplay(uiAutomation, activityTitle, displayId);
-                    if (window == null) return false;
-                    window.getBoundsInScreen(bounds);
-                    mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
-                    if (bounds.isEmpty()) {
-                        return false;
-                    }
-                    return (!bounds.isEmpty())
-                            && (bounds.left == location[0]) && (bounds.top == location[1]);
-                }, DEFAULT_TIMEOUT_MS);
-        assertNotNull(awaitedEvent);
+
+                        // Stores the related information including event, location and window
+                        // as a timeout exception record.
+                        timeoutExceptionRecords.append(String.format("{Received event: %s \n"
+                                + "Window location: %s \nA11y window: %s}\n",
+                                event, Arrays.toString(location), window));
+
+                        return (!bounds.isEmpty())
+                                && (bounds.left == location[0]) && (bounds.top == location[1]);
+                    }, DEFAULT_TIMEOUT_MS);
+            assertNotNull(awaitedEvent);
+        } catch (TimeoutException timeout) {
+            throw new TimeoutException(timeout.getMessage() + "\n\nTimeout exception records : \n"
+                    + timeoutExceptionRecords);
+        }
         return (T) mTempActivity;
     }
 
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml b/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml
index e1628bf..af7ae8f 100644
--- a/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml
@@ -16,16 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="foo.bar.baz"
-          android:targetSandboxVersion="2">
+     package="foo.bar.baz"
+     android:targetSandboxVersion="2">
 
     <application>
-        <receiver android:name="foo.bar.baz.MyAppWidgetProvider" >
+        <receiver android:name="foo.bar.baz.MyAppWidgetProvider"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
             </intent-filter>
             <meta-data android:name="android.appwidget.provider"
-                       android:resource="@xml/appwidget_info" />
+                 android:resource="@xml/appwidget_info"/>
         </receiver>
     </application>
 
diff --git a/tests/accessibilityservice/testsdk29/AndroidManifest.xml b/tests/accessibilityservice/testsdk29/AndroidManifest.xml
index 90b2f5f..ad47ad7 100644
--- a/tests/accessibilityservice/testsdk29/AndroidManifest.xml
+++ b/tests/accessibilityservice/testsdk29/AndroidManifest.xml
@@ -16,37 +16,34 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.accessibilityservice.cts.testsdk29">
+     package="android.accessibilityservice.cts.testsdk29">
 
-    <uses-sdk android:targetSdkVersion="29" />
+    <uses-sdk android:targetSdkVersion="29"/>
 
     <application android:theme="@android:style/Theme.Holo.NoActionBar"
-                 android:requestLegacyExternalStorage="true">
+         android:requestLegacyExternalStorage="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <service
-            android:name="android.accessibilityservice.cts.StubAccessibilityButtonSdk29Service"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name="android.accessibilityservice.cts.StubAccessibilityButtonSdk29Service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_accessibility_button_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_accessibility_button_service"/>
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.accessibilityservice.cts.testsdk29"
-        android:label="Tests for the accessibility Sdk 29 APIs.">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.accessibilityservice.cts.testsdk29"
+         android:label="Tests for the accessibility Sdk 29 APIs.">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/admin/AdminWorkProfileTest.xml b/tests/admin/AdminWorkProfileTest.xml
new file mode 100644
index 0000000..af93ea1
--- /dev/null
+++ b/tests/admin/AdminWorkProfileTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Config for CTS Device Admin test cases on a work profile">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
+         here are not applicable -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="CtsAdminApp.apk" />
+        <option name="test-file-name" value="CtsAdminTestCases.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer">
+        <option name="test-package-name" value="android.admin.cts" />
+        <option name="test-package-name" value="android.admin.app" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.admin.cts" />
+        <option name="include-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
+        <!--        <option name="instrumentation-arg" key="skip-test-teardown" value="true" />-->
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/admin/Android.bp b/tests/admin/Android.bp
index a9bf3c3..169cd71 100644
--- a/tests/admin/Android.bp
+++ b/tests/admin/Android.bp
@@ -16,6 +16,7 @@
     name: "CtsAdminTestCases",
     defaults: ["cts_defaults"],
     static_libs: [
+        "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "mockito-target-minus-junit4",
         "truth-prebuilt",
@@ -31,6 +32,9 @@
         "cts",
         "general-tests",
     ],
+    test_options: {
+        extra_test_configs: ["AdminWorkProfileTest.xml"]
+    },
     instrumentation_for: "CtsAdminApp",
     sdk_version: "test_current",
 }
diff --git a/tests/admin/AndroidTest.xml b/tests/admin/AndroidTest.xml
index 66070cb..b1acd9a 100644
--- a/tests/admin/AndroidTest.xml
+++ b/tests/admin/AndroidTest.xml
@@ -49,6 +49,7 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.admin.cts" />
         <option name="runtime-hint" value="17m" />
+        <option name="exclude-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
     </test>
 
 </configuration>
diff --git a/tests/admin/app/AndroidManifest.xml b/tests/admin/app/AndroidManifest.xml
index baff9ab..c0eee88 100644
--- a/tests/admin/app/AndroidManifest.xml
+++ b/tests/admin/app/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2011 The Android Open Source Project
  *
@@ -17,139 +16,152 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.admin.app">
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28"/>
+     package="android.admin.app">
+    <uses-sdk android:minSdkVersion="19"
+         android:targetSdkVersion="28"/>
 
     <application android:testOnly="true">
 
         <uses-library android:name="android.test.runner"/>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminDeviceOwner"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminProfileOwner"
-                  android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminReceiver2"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin_2" />
+                 android:resource="@xml/device_admin_2"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminReceiver3"
-                  android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin_3" />
+                 android:resource="@xml/device_admin_3"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminReceiverVisible"
-                  android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin_visible" />
+                 android:resource="@xml/device_admin_visible"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminReceiverInvisible"
-                  android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin_invisible" />
+                 android:resource="@xml/device_admin_invisible"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <!-- Device Admin that needs to be in the deactivated state in order
-             for tests to pass. -->
+                         for tests to pass. -->
         <receiver android:name="android.admin.app.CtsDeviceAdminDeactivatedReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <!-- Helper Activity used by Device Admin activation tests -->
         <activity android:name="android.admin.app.CtsDeviceAdminActivationTestActivity"
-                android:label="Device Admin activation test" />
+             android:label="Device Admin activation test"/>
 
         <!-- Broken device admin: meta-data missing -->
         <receiver android:name="android.admin.app.CtsDeviceAdminBrokenReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <!-- Broken device admin: filter doesn't match an Intent with action
-             android.app.action.DEVICE_ADMIN_ENABLED and nothing else (e.g.,
-             data) set -->
+                         android.app.action.DEVICE_ADMIN_ENABLED and nothing else (e.g.,
+                         data) set -->
         <receiver android:name="android.admin.app.CtsDeviceAdminBrokenReceiver2"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
-                <data android:scheme="https" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+                <data android:scheme="https"/>
             </intent-filter>
         </receiver>
 
         <!-- Broken device admin: meta-data element doesn't point to valid
-             Device Admin configuration/description -->
+                         Device Admin configuration/description -->
         <receiver android:name="android.admin.app.CtsDeviceAdminBrokenReceiver3"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/broken_device_admin" />
+                 android:resource="@xml/broken_device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <!-- Broken device admin: filter doesn't match Intents with action
-             android.app.action.DEVICE_ADMIN_ENABLED -->
+                         android.app.action.DEVICE_ADMIN_ENABLED -->
         <receiver android:name="android.admin.app.CtsDeviceAdminBrokenReceiver4"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_DISABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_DISABLED"/>
             </intent-filter>
         </receiver>
 
         <!-- Broken device admin: no intent-filter -->
         <receiver android:name="android.admin.app.CtsDeviceAdminBrokenReceiver5"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
         </receiver>
 
     </application>
diff --git a/tests/admin/src/android/admin/cts/DeviceAdminTempTest.java b/tests/admin/src/android/admin/cts/DeviceAdminTempTest.java
new file mode 100644
index 0000000..8c840b8
--- /dev/null
+++ b/tests/admin/src/android/admin/cts/DeviceAdminTempTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.admin.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.compatibility.common.util.enterprise.Preconditions;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.os.UserManager;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceAdminTempTest {
+
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+    @ClassRule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    @Rule
+    public final Preconditions mPreconditions = new Preconditions(sDeviceState);
+
+    @RequireRunOnWorkProfile
+    @Test
+    public void testRunningOnWorkProfile() {
+        assertThat(sContext.getSystemService(UserManager.class).isManagedProfile()).isTrue();
+    }
+}
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index 1c38b39..d1085c7 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -684,6 +684,18 @@
         }
     }
 
+    public void testSetUninstallBlocked_succeedForNotInstalledApps() {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testSetUninstallBlocked_succeedForNotInstalledApps");
+            return;
+        }
+        ComponentName profileOwner = DeviceAdminInfoTest.getProfileOwnerComponent();
+        mDevicePolicyManager.setUninstallBlocked(profileOwner,
+                "android.admin.not.installed", true);
+        assertFalse(mDevicePolicyManager.isUninstallBlocked(profileOwner,
+              "android.admin.not.installed"));
+    }
+
     public void testSetPermittedAccessibilityServices_failIfNotProfileOwner() {
         if (!mDeviceAdmin) {
             Log.w(TAG, "Skipping testSetPermittedAccessibilityServices_failIfNotProfileOwner");
@@ -774,23 +786,28 @@
 
     private void assertDeviceOwnerMessage(String message) {
         assertTrue("message is: "+ message, message.contains("does not own the device")
-                || message.contains("can only be called by the device owner"));
+                || message.contains("can only be called by the device owner")
+                || message.contains("Calling identity is not authorized"));
     }
 
     private void assertOrganizationOwnedProfileOwnerMessage(String message) {
-        assertTrue("message is: "+ message,
-                message.contains("is not the profile owner on organization-owned device"));
+        assertTrue("message is: " + message, message.contains(
+                "is not the profile owner on organization-owned device")
+                || message.contains("Calling identity is not authorized"));
     }
 
     private void assertDeviceOwnerOrManageUsersMessage(String message) {
         assertTrue("message is: "+ message, message.contains("does not own the device")
                 || message.contains("can only be called by the device owner")
                 || (message.startsWith("Neither user ") && message.endsWith(
-                        " nor current process has android.permission.MANAGE_USERS.")));
+                        " nor current process has android.permission.MANAGE_USERS."))
+                || message.contains("Calling identity is not authorized"));
     }
 
     private void assertProfileOwnerMessage(String message) {
-        assertTrue("message is: "+ message, message.contains("does not own the profile"));
+        assertTrue("message is: "+ message, message.contains("does not own the profile")
+                || message.contains("is not profile owner")
+                || message.contains("Calling identity is not authorized"));
     }
 
     public void testSetDelegatedCertInstaller_failIfNotProfileOwner() {
diff --git a/tests/app/ActivityManagerApi29Test/AndroidManifest.xml b/tests/app/ActivityManagerApi29Test/AndroidManifest.xml
index 0c75ff4..79a9020 100644
--- a/tests/app/ActivityManagerApi29Test/AndroidManifest.xml
+++ b/tests/app/ActivityManagerApi29Test/AndroidManifest.xml
@@ -16,27 +16,30 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.app.cts.activitymanager.api29">
-    <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="29" />
+     package="android.app.cts.activitymanager.api29">
+    <uses-sdk android:minSdkVersion="11"
+         android:targetSdkVersion="29"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
 
     <application android:usesCleartextTraffic="true">
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
         <activity android:name=".SimpleActivity"
-                  android:excludeFromRecents="true">
+             android:excludeFromRecents="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <service android:name="LocationForegroundService"
-            android:foregroundServiceType="location|camera|microphone"
-            android:exported="true">
+             android:foregroundServiceType="location|camera|microphone"
+             android:exported="true">
         </service>
     </application>
 </manifest>
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index 465b633..8033746 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -37,6 +37,8 @@
         <option name="test-file-name" value="NotificationListener.apk" />
         <option name="test-file-name" value="StorageDelegator.apk" />
         <option name="test-file-name" value="CtsActivityManagerApi29.apk" />
+        <option name="test-file-name" value="NotificationTrampoline.apk" />
+        <option name="test-file-name" value="NotificationTrampolineApi30.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
diff --git a/tests/app/CantSaveState1/AndroidManifest.xml b/tests/app/CantSaveState1/AndroidManifest.xml
index fadcaeb..41aad1f 100644
--- a/tests/app/CantSaveState1/AndroidManifest.xml
+++ b/tests/app/CantSaveState1/AndroidManifest.xml
@@ -13,14 +13,21 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.cantsavestate1">
-    <application android:label="Can't Save 1" android:cantSaveState="true">
-        <activity android:name="CantSave1Activity">
+     package="com.android.test.cantsavestate1">
+    <application android:label="Can't Save 1"
+         android:cantSaveState="true">
+        <activity android:name="CantSave1Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="com.android.test.action.FINISH"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java b/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
index fb678cb..9a9388e 100644
--- a/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
+++ b/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
@@ -17,13 +17,24 @@
 package com.android.test.cantsavestate1;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 
 public class CantSave1Activity extends Activity {
+
+    public static final String ACTION_FINISH = "com.android.test.action.FINISH";
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.cant_save_1_activity);
         getWindow().getDecorView().requestFocus();
     }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        if (ACTION_FINISH.equals(intent.getAction())) {
+            finish();
+        }
+    }
 }
diff --git a/tests/app/CantSaveState2/AndroidManifest.xml b/tests/app/CantSaveState2/AndroidManifest.xml
index 8f4f01d..92b059d 100644
--- a/tests/app/CantSaveState2/AndroidManifest.xml
+++ b/tests/app/CantSaveState2/AndroidManifest.xml
@@ -13,14 +13,17 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.cantsavestate2">
-    <application android:label="Can't Save 2" android:cantSaveState="true">
-        <activity android:name="CantSave2Activity">
+     package="com.android.test.cantsavestate2">
+    <application android:label="Can't Save 2"
+         android:cantSaveState="true">
+        <activity android:name="CantSave2Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/app/DownloadManagerApi28Test/AndroidManifest.xml b/tests/app/DownloadManagerApi28Test/AndroidManifest.xml
index fec3c4d..1d59250 100644
--- a/tests/app/DownloadManagerApi28Test/AndroidManifest.xml
+++ b/tests/app/DownloadManagerApi28Test/AndroidManifest.xml
@@ -23,6 +23,7 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 
     <application android:usesCleartextTraffic="true"
                  android:networkSecurityConfig="@xml/network_security_config">
diff --git a/tests/app/DownloadManagerInstallerTest/AndroidManifest.xml b/tests/app/DownloadManagerInstallerTest/AndroidManifest.xml
index cb0b73b..c2424be 100644
--- a/tests/app/DownloadManagerInstallerTest/AndroidManifest.xml
+++ b/tests/app/DownloadManagerInstallerTest/AndroidManifest.xml
@@ -20,6 +20,7 @@
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 
     <application android:usesCleartextTraffic="true"
                  android:networkSecurityConfig="@xml/network_security_config">
diff --git a/tests/app/NotificationDelegator/AndroidManifest.xml b/tests/app/NotificationDelegator/AndroidManifest.xml
index fbdf219..a05dcd2 100644
--- a/tests/app/NotificationDelegator/AndroidManifest.xml
+++ b/tests/app/NotificationDelegator/AndroidManifest.xml
@@ -13,28 +13,32 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.notificationdelegator">
+     package="com.android.test.notificationdelegator">
     <application android:label="Notification Delegator">
-        <activity android:name="com.android.test.notificationdelegator.NotificationDelegator">
+        <activity android:name="com.android.test.notificationdelegator.NotificationDelegator"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.test.notificationdelegator.NotificationRevoker">
+        <activity android:name="com.android.test.notificationdelegator.NotificationRevoker"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.test.notificationdelegator.NotificationDelegateAndPost">
+        <activity android:name="com.android.test.notificationdelegator.NotificationDelegateAndPost"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
diff --git a/tests/app/NotificationTrampoline/Android.bp b/tests/app/NotificationTrampoline/Android.bp
new file mode 100644
index 0000000..0b72924
--- /dev/null
+++ b/tests/app/NotificationTrampoline/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "NotificationTrampoline",
+    defaults: ["cts_support_defaults"],
+    srcs: [
+        "**/*.java",
+    ],
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    static_libs: [
+        "NotificationTrampolineBase",
+    ],
+    sdk_version: "test_current",
+}
+
diff --git a/tests/app/NotificationTrampoline/AndroidManifest.xml b/tests/app/NotificationTrampoline/AndroidManifest.xml
new file mode 100644
index 0000000..97cbc42
--- /dev/null
+++ b/tests/app/NotificationTrampoline/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.test.notificationtrampoline.current">
+    <!-- App is in NotificationTrampolineBase -->
+</manifest>
diff --git a/tests/app/NotificationTrampolineApi30/Android.bp b/tests/app/NotificationTrampolineApi30/Android.bp
new file mode 100644
index 0000000..e546429
--- /dev/null
+++ b/tests/app/NotificationTrampolineApi30/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "NotificationTrampolineApi30",
+    defaults: ["cts_support_defaults"],
+    srcs: [
+        "**/*.java",
+    ],
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    static_libs: [
+        "NotificationTrampolineBase",
+    ],
+    sdk_version: "test_current",
+    target_sdk_version: "30",
+}
diff --git a/tests/app/NotificationTrampolineApi30/AndroidManifest.xml b/tests/app/NotificationTrampolineApi30/AndroidManifest.xml
new file mode 100644
index 0000000..b24b2f1
--- /dev/null
+++ b/tests/app/NotificationTrampolineApi30/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.test.notificationtrampoline.api30">
+    <!-- App is in NotificationTrampolineBase -->
+</manifest>
diff --git a/tests/app/NotificationTrampolineBase/Android.bp b/tests/app/NotificationTrampolineBase/Android.bp
new file mode 100644
index 0000000..0b2a0fe
--- /dev/null
+++ b/tests/app/NotificationTrampolineBase/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_library {
+    name: "NotificationTrampolineBase",
+    defaults: ["cts_support_defaults"],
+    srcs: [
+        "**/*.java",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "platform-test-annotations",
+    ],
+}
diff --git a/tests/app/NotificationTrampolineBase/AndroidManifest.xml b/tests/app/NotificationTrampolineBase/AndroidManifest.xml
new file mode 100644
index 0000000..093495f
--- /dev/null
+++ b/tests/app/NotificationTrampolineBase/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.test.notificationtrampoline">
+    <application>
+        <service
+            android:name=".NotificationTrampolineTestService"
+            android:exported="true" />
+        <activity
+            android:name=".NotificationTrampolineTestService$TargetActivity"
+            android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
new file mode 100644
index 0000000..1149e5e
--- /dev/null
+++ b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.notificationtrampoline;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.ArraySet;
+
+import androidx.annotation.Nullable;
+
+import java.lang.ref.WeakReference;
+import java.util.Set;
+
+/**
+ * This is a bound service used in conjunction with trampoline tests in NotificationManagerTest.
+ */
+public class NotificationTrampolineTestService extends Service {
+    private static final String TAG = "TrampolineTestService";
+    private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
+    private static final String EXTRA_CALLBACK = "callback";
+    private static final String EXTRA_ACTIVITY_REF = "activity_ref";
+    private static final String RECEIVER_ACTION = ".TRAMPOLINE";
+    private static final int MESSAGE_BROADCAST_NOTIFICATION = 1;
+    private static final int MESSAGE_SERVICE_NOTIFICATION = 2;
+    private static final int TEST_MESSAGE_BROADCAST_RECEIVED = 1;
+    private static final int TEST_MESSAGE_SERVICE_STARTED = 2;
+    private static final int TEST_MESSAGE_ACTIVITY_STARTED = 3;
+
+    private final Handler mHandler = new ServiceHandler();
+    private final ActivityReference mActivityRef = new ActivityReference();
+    private final Set<Integer> mPostedNotifications = new ArraySet<>();
+    private NotificationManager mNotificationManager;
+    private Messenger mMessenger;
+    private BroadcastReceiver mReceiver;
+    private Messenger mCallback;
+    private String mReceiverAction;
+
+    @Override
+    public void onCreate() {
+        mNotificationManager = getSystemService(NotificationManager.class);
+        mMessenger = new Messenger(mHandler);
+        mReceiverAction = getPackageName() + RECEIVER_ACTION;
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mReceiver != null) {
+            unregisterReceiver(mReceiver);
+        }
+        WeakReference<Activity> activityRef = mActivityRef.activity;
+        Activity activity = (activityRef != null) ? activityRef.get() : null;
+        if (activity != null) {
+            activity.finish();
+        }
+        for (int notificationId : mPostedNotifications) {
+            mNotificationManager.cancel(notificationId);
+        }
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    /** Suppressing since all messages are short-lived and we clear the queue on exit. */
+    @SuppressLint("HandlerLeak")
+    private class ServiceHandler extends Handler {
+        @Override
+        public void handleMessage(Message message) {
+            Context context = NotificationTrampolineTestService.this;
+            mCallback = (Messenger) message.obj;
+            int notificationId = message.arg1;
+            switch (message.what) {
+                case MESSAGE_BROADCAST_NOTIFICATION: {
+                    mReceiver = new BroadcastReceiver() {
+                        @Override
+                        public void onReceive(Context context, Intent broadcastIntent) {
+                            sendMessageToTest(mCallback, TEST_MESSAGE_BROADCAST_RECEIVED);
+                            startTargetActivity();
+                        }
+                    };
+                    registerReceiver(mReceiver, new IntentFilter(mReceiverAction));
+                    Intent intent = new Intent(mReceiverAction);
+                    postNotification(notificationId,
+                            PendingIntent.getBroadcast(context, 0, intent, 0));
+                    break;
+                }
+                case MESSAGE_SERVICE_NOTIFICATION: {
+                    // We use this service to act as the trampoline since the bound lifecycle (which
+                    // is as long as the test is being executed) outlives the started (used by the
+                    // trampoline) in this case.
+                    Intent intent = new Intent(context, NotificationTrampolineTestService.class);
+                    postNotification(notificationId,
+                            PendingIntent.getService(context, 0, intent, 0));
+                    break;
+                }
+                default:
+                    throw new AssertionError("Unknown message " + message.what);
+            }
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent serviceIntent, int flags, int startId) {
+        sendMessageToTest(mCallback, TEST_MESSAGE_SERVICE_STARTED);
+        startTargetActivity();
+        stopSelf(startId);
+        return START_REDELIVER_INTENT;
+    }
+
+    private void postNotification(int notificationId, PendingIntent intent) {
+        Notification notification =
+                new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(android.R.drawable.ic_info)
+                        .setContentIntent(intent)
+                        .build();
+        NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+        mNotificationManager.createNotificationChannel(notificationChannel);
+        mNotificationManager.notify(notificationId, notification);
+        mPostedNotifications.add(notificationId);
+    }
+
+    private void startTargetActivity() {
+        Intent intent = new Intent(this, TargetActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_CALLBACK, mCallback);
+        extras.putBinder(EXTRA_ACTIVITY_REF, mActivityRef);
+        intent.putExtras(extras);
+        startActivity(intent);
+    }
+
+    private static void sendMessageToTest(Messenger callback, int message) {
+        try {
+            callback.send(Message.obtain(null, message));
+        } catch (RemoteException e) {
+            throw new IllegalStateException(
+                    "Couldn't send message " + message + " to test process", e);
+        }
+    }
+
+    /**
+     * A holder object that extends from Binder just so I can send it around using startActivity()
+     * and avoid using static state. Works since the communication is local.
+     */
+    private static class ActivityReference extends Binder {
+        public WeakReference<Activity> activity;
+    }
+
+    public static class TargetActivity extends Activity {
+        @Override
+        protected void onResume() {
+            super.onResume();
+            Messenger callback = getIntent().getParcelableExtra(EXTRA_CALLBACK);
+            ActivityReference activityRef =
+                    (ActivityReference) getIntent().getExtras().getBinder(EXTRA_ACTIVITY_REF);
+            activityRef.activity = new WeakReference<>(this);
+            sendMessageToTest(callback, TEST_MESSAGE_ACTIVITY_STARTED);
+        }
+    }
+}
diff --git a/tests/app/StorageDelegator/AndroidManifest.xml b/tests/app/StorageDelegator/AndroidManifest.xml
index c252a80..7812a23 100644
--- a/tests/app/StorageDelegator/AndroidManifest.xml
+++ b/tests/app/StorageDelegator/AndroidManifest.xml
@@ -13,20 +13,22 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.test.storagedelegator">
+     package="com.android.test.storagedelegator">
 
-    <uses-sdk android:targetSdkVersion="28" />
+    <uses-sdk android:targetSdkVersion="28"/>
 
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 
     <application android:label="StorageDelegator">
         <activity android:name=".StorageDelegator"
-                android:theme="@android:style/Theme.NoDisplay">
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.action.CREATE_FILE_WITH_CONTENT" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.action.CREATE_FILE_WITH_CONTENT"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index a6fe151..90375cb 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -16,75 +16,91 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.app.stubs">
+     package="android.app.stubs">
 
     <permission android:name="android.app.stubs.permission.TEST_GRANTED"
-        android:protectionLevel="normal"
-            android:label="@string/permlab_testGranted"
-            android:description="@string/permdesc_testGranted">
-        <meta-data android:name="android.app.stubs.string" android:value="foo" />
-        <meta-data android:name="android.app.stubs.boolean" android:value="true" />
-        <meta-data android:name="android.app.stubs.integer" android:value="100" />
-        <meta-data android:name="android.app.stubs.color" android:value="#ff000000" />
-        <meta-data android:name="android.app.stubs.float" android:value="100.1" />
-        <meta-data android:name="android.app.stubs.reference" android:resource="@xml/metadata" />
+         android:protectionLevel="normal"
+         android:label="@string/permlab_testGranted"
+         android:description="@string/permdesc_testGranted">
+        <meta-data android:name="android.app.stubs.string"
+             android:value="foo"/>
+        <meta-data android:name="android.app.stubs.boolean"
+             android:value="true"/>
+        <meta-data android:name="android.app.stubs.integer"
+             android:value="100"/>
+        <meta-data android:name="android.app.stubs.color"
+             android:value="#ff000000"/>
+        <meta-data android:name="android.app.stubs.float"
+             android:value="100.1"/>
+        <meta-data android:name="android.app.stubs.reference"
+             android:resource="@xml/metadata"/>
     </permission>
 
-    <uses-permission android:name="android.app.stubs.permission.TEST_GRANTED" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-    <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.BODY_SENSORS" />
-    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
-    <uses-permission android:name="android.permission.SET_WALLPAPER" />
+    <queries>
+        <package android:name="com.android.test.notificationtrampoline.current" />
+        <package android:name="com.android.test.notificationtrampoline.api30" />
+    </queries>
+
+    <uses-permission android:name="android.app.stubs.permission.TEST_GRANTED"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.BODY_SENSORS"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.SET_WALLPAPER"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
-    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 
     <application android:label="Android TestCase"
-                android:icon="@drawable/size_48x48"
-                android:maxRecents="1"
-                android:multiArch="true"
-                android:name="android.app.stubs.MockApplication"
-                android:supportsRtl="true"
-                android:networkSecurityConfig="@xml/network_security_config"
-                android:zygotePreloadName=".ZygotePreload">
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+         android:icon="@drawable/size_48x48"
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:name="android.app.stubs.MockApplication"
+         android:supportsRtl="true"
+         android:networkSecurityConfig="@xml/network_security_config"
+         android:zygotePreloadName=".ZygotePreload">
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
 
-        <activity android:name="android.app.stubs.ScreenOnActivity" />
+        <activity android:name="android.app.stubs.ScreenOnActivity"/>
 
-        <activity android:name="android.app.stubs.ActionBarActivity" />
+        <activity android:name="android.app.stubs.ActionBarActivity"/>
 
-        <activity android:name="android.app.stubs.ActivityCallbacksTestActivity" />
+        <activity android:name="android.app.stubs.ActivityCallbacksTestActivity"/>
 
-        <activity android:name="android.app.stubs.MockActivity" android:label="MockActivity">
+        <activity android:name="android.app.stubs.MockActivity"
+             android:label="MockActivity">
             <meta-data android:name="android.app.alias"
-                android:resource="@xml/alias" />
+                 android:resource="@xml/alias"/>
             <meta-data android:name="android.app.intent.filter"
-                android:resource="@xml/intentfilter" />
+                 android:resource="@xml/intentfilter"/>
         </activity>
 
         <activity android:name="android.app.stubs.MockApplicationActivity"
-            android:label="MockApplicationActivity">
+             android:label="MockApplicationActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.InstrumentationTestActivity"
-                  android:label="InstrumentationTestActivity">
+             android:label="InstrumentationTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="vnd.android.cursor.dir/person" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="vnd.android.cursor.dir/person"/>
             </intent-filter>
             <intent-filter>
                 <action android:name="android.app.stubs.activity.INSTRUMENTATION_TEST"/>
@@ -92,343 +108,385 @@
         </activity>
 
         <activity android:name="android.app.stubs.ActivityMonitorTestActivity"
-                  android:label="ActivityMonitorTestActivity" />
+             android:label="ActivityMonitorTestActivity"/>
 
         <activity android:name="android.app.stubs.AliasActivityStub">
             <meta-data android:name="android.app.alias"
-                android:resource="@xml/alias" />
+                 android:resource="@xml/alias"/>
         </activity>
 
         <activity android:name="android.app.stubs.ChildActivity"
-                        android:label="ChildActivity" />
+             android:label="ChildActivity"/>
 
-        <receiver android:name="android.app.stubs.MockReceiver">
+        <receiver android:name="android.app.stubs.MockReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.PendingIntentTest.TEST_RECEIVER" />
+                <action android:name="android.app.stubs.PendingIntentTest.TEST_RECEIVER"/>
             </intent-filter>
         </receiver>
 
-        <service android:name="android.app.stubs.MockService" />
+        <service android:name="android.app.stubs.MockService"/>
 
-        <service android:name="android.app.stubs.NullService" />
+        <service android:name="android.app.stubs.NullService"/>
 
         <activity android:name="android.app.stubs.SearchManagerStubActivity"
-                android:label="SearchManagerStubActivity">
+             android:label="SearchManagerStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.SEARCH"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
-            <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable"/>
         </activity>
 
-        <service android:name="android.app.stubs.LocalService">
+        <service android:name="android.app.stubs.LocalService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.activity.SERVICE_LOCAL" />
+                <action android:name="android.app.stubs.activity.SERVICE_LOCAL"/>
             </intent-filter>
-            <meta-data android:name="android.app.stubs.string" android:value="foo" />
-            <meta-data android:name="android.app.stubs.boolean" android:value="true" />
-            <meta-data android:name="android.app.stubs.integer" android:value="100" />
-            <meta-data android:name="android.app.stubs.color" android:value="#ff000000" />
-            <meta-data android:name="android.app.stubs.float" android:value="100.1" />
-            <meta-data android:name="android.app.stubs.reference" android:resource="@xml/metadata" />
+            <meta-data android:name="android.app.stubs.string"
+                 android:value="foo"/>
+            <meta-data android:name="android.app.stubs.boolean"
+                 android:value="true"/>
+            <meta-data android:name="android.app.stubs.integer"
+                 android:value="100"/>
+            <meta-data android:name="android.app.stubs.color"
+                 android:value="#ff000000"/>
+            <meta-data android:name="android.app.stubs.float"
+                 android:value="100.1"/>
+            <meta-data android:name="android.app.stubs.reference"
+                 android:resource="@xml/metadata"/>
         </service>
 
-        <service android:name="android.app.stubs.LocalStoppedService" />
+        <service android:name="android.app.stubs.LocalStoppedService"/>
 
         <service android:name="android.app.stubs.LocalForegroundService"
-                 android:foregroundServiceType="camera|microphone">
+             android:foregroundServiceType="camera|microphone"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.FOREGROUND_SERVICE" />
+                <action android:name="android.app.stubs.FOREGROUND_SERVICE"/>
             </intent-filter>
         </service>
 
         <service android:name="android.app.stubs.LocalForegroundServiceLocation"
-                android:foregroundServiceType="location|camera|microphone">
+             android:foregroundServiceType="location|camera|microphone"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.FOREGROUND_SERVICE_LOCATION" />
+                <action android:name="android.app.stubs.FOREGROUND_SERVICE_LOCATION"/>
             </intent-filter>
         </service>
 
         <service android:name="android.app.stubs.LocalGrantedService"
-             android:permission="android.app.stubs.permission.TEST_GRANTED">
+             android:permission="android.app.stubs.permission.TEST_GRANTED"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.activity.SERVICE_LOCAL_GRANTED" />
+                <action android:name="android.app.stubs.activity.SERVICE_LOCAL_GRANTED"/>
             </intent-filter>
         </service>
 
         <service android:name="android.app.stubs.LocalDeniedService"
-               android:permission="android.app.stubs.permission.TEST_DENIED">
+             android:permission="android.app.stubs.permission.TEST_DENIED"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.activity.SERVICE_LOCAL_DENIED" />
+                <action android:name="android.app.stubs.activity.SERVICE_LOCAL_DENIED"/>
             </intent-filter>
         </service>
 
-        <service android:name="android.app.stubs.IsolatedService" android:isolatedProcess="true" android:useAppZygote="true">
+        <service android:name="android.app.stubs.IsolatedService"
+             android:isolatedProcess="true"
+             android:useAppZygote="true">
         </service>
 
         <activity android:name="android.app.stubs.TestedScreen"
-                android:process=":remoteScreen">
+             android:process=":remoteScreen">
         </activity>
-        <activity android:name="android.app.stubs.LocalScreen" android:multiprocess="true">
+        <activity android:name="android.app.stubs.LocalScreen"
+             android:multiprocess="true">
         </activity>
-        <activity android:name="android.app.stubs.ClearTop" android:multiprocess="true"
-               android:launchMode="singleTop">
+        <activity android:name="android.app.stubs.ClearTop"
+             android:multiprocess="true"
+             android:launchMode="singleTop">
         </activity>
-        <activity android:name="android.app.stubs.LocalDialog" android:multiprocess="true"
-               android:theme="@android:style/Theme.Dialog">
+        <activity android:name="android.app.stubs.LocalDialog"
+             android:multiprocess="true"
+             android:theme="@android:style/Theme.Dialog">
         </activity>
 
         <activity android:name="android.app.stubs.PendingIntentStubActivity"
              android:label="PendingIntentStubActivity"/>
 
         <activity android:name="android.app.stubs.LocalActivityManagerStubActivity"
-                        android:label="LocalActivityManagerStubActivity" />
+             android:label="LocalActivityManagerStubActivity"/>
 
         <activity android:name="android.app.stubs.LocalActivityManagerTestHelper"
-            android:label="LocalActivityManagerTestHelper" />
+             android:label="LocalActivityManagerTestHelper"/>
 
-        <activity android:name="android.app.stubs.LaunchpadTabActivity" android:multiprocess="true">
+        <activity android:name="android.app.stubs.LaunchpadTabActivity"
+             android:multiprocess="true">
         </activity>
 
-        <activity android:name="android.app.stubs.LocalActivity" android:multiprocess="true">
-            <meta-data android:name="android.app.stubs.string" android:value="foo" />
-            <meta-data android:name="android.app.stubs.boolean" android:value="true" />
-            <meta-data android:name="android.app.stubs.integer" android:value="100" />
-            <meta-data android:name="android.app.stubs.color" android:value="#ff000000" />
-            <meta-data android:name="android.app.stubs.float" android:value="100.1" />
-            <meta-data android:name="android.app.stubs.reference" android:resource="@xml/metadata" />
+        <activity android:name="android.app.stubs.LocalActivity"
+             android:multiprocess="true">
+            <meta-data android:name="android.app.stubs.string"
+                 android:value="foo"/>
+            <meta-data android:name="android.app.stubs.boolean"
+                 android:value="true"/>
+            <meta-data android:name="android.app.stubs.integer"
+                 android:value="100"/>
+            <meta-data android:name="android.app.stubs.color"
+                 android:value="#ff000000"/>
+            <meta-data android:name="android.app.stubs.float"
+                 android:value="100.1"/>
+            <meta-data android:name="android.app.stubs.reference"
+                 android:resource="@xml/metadata"/>
         </activity>
 
         <activity android:name="android.app.stubs.TestedActivity"
-                android:process=":remoteActivity">
+             android:process=":remoteActivity">
         </activity>
 
         <activity android:name="android.app.stubs.ExpandableListTestActivity"
-            android:label="ExpandableListTestActivity">
+             android:label="ExpandableListTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.FragmentTestActivity"
-            android:label="FragmentTestActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-
-        <activity android:name="android.app.stubs.FragmentResultActivity" android:label="FragmentResultActivity" />
-
-        <activity android:name="android.app.stubs.LauncherActivityStub"
-                  android:label="LauncherActivityStub" >
+             android:label="FragmentTestActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.app.stubs.MockTabActivity" android:label="MockTabActivity" />
+        <activity android:name="android.app.stubs.FragmentResultActivity"
+             android:label="FragmentResultActivity"/>
 
-        <activity android:name="android.app.stubs.MockListActivity" android:label="MockListActivity" />
-
-        <activity android:name="android.app.stubs.AppStubActivity" android:label="AppStubActivity">
+        <activity android:name="android.app.stubs.LauncherActivityStub"
+             android:label="LauncherActivityStub"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.app.stubs.MockTabActivity"
+             android:label="MockTabActivity"/>
+
+        <activity android:name="android.app.stubs.MockListActivity"
+             android:label="MockListActivity"/>
+
+        <activity android:name="android.app.stubs.AppStubActivity"
+             android:label="AppStubActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.DialogStubActivity"
-                  android:label="DialogStubActivity">
+             android:label="DialogStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.ActivityManagerStubFooActivity"
-            android:label="ActivityManagerStubFooActivity">
+             android:label="ActivityManagerStubFooActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.ActivityManagerRecentOneActivity"
-            android:label="ActivityManagerRecentOneActivity"
-            android:allowTaskReparenting="true"
-            android:taskAffinity="android.app.stubs.recentOne">
+             android:label="ActivityManagerRecentOneActivity"
+             android:allowTaskReparenting="true"
+             android:taskAffinity="android.app.stubs.recentOne"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.ActivityManagerRecentTwoActivity"
-            android:label="ActivityManagerRecentTwoActivity"
-            android:allowTaskReparenting="true"
-            android:taskAffinity="android.app.stubs.recentTwo">
+             android:label="ActivityManagerRecentTwoActivity"
+             android:allowTaskReparenting="true"
+             android:taskAffinity="android.app.stubs.recentTwo"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.ActivityManagerStubCrashActivity"
-            android:label="ActivityManagerStubCrashActivity"
-            android:multiprocess="true"
-            android:process=":ActivityManagerStubCrashActivity">
+             android:label="ActivityManagerStubCrashActivity"
+             android:multiprocess="true"
+             android:process=":ActivityManagerStubCrashActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
         <service android:name="android.app.stubs.StubRemoteService"
-            android:process=":remote">
+             android:process=":remote"
+             android:exported="true">
             <intent-filter>
-                <action
-                    android:name="android.app.stubs.ISecondary" />
-                <action
-                    android:name="android.app.REMOTESERVICE" />
+                <action android:name="android.app.stubs.ISecondary"/>
+                <action android:name="android.app.REMOTESERVICE"/>
             </intent-filter>
         </service>
 
         <activity android:name="android.app.ActivityGroup"
-            android:label="ActivityGroup" />
+             android:label="ActivityGroup"/>
 
         <activity android:name="android.app.stubs.KeyguardManagerActivity"
-            android:label="KeyguardManagerActivity">
+             android:label="KeyguardManagerActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <service android:name="android.app.stubs.IntentServiceStub"/>
 
         <activity android:name="android.app.stubs.LaunchpadActivity"
-                  android:configChanges="keyboardHidden|orientation|screenSize"
-                  android:multiprocess="true">
+             android:configChanges="keyboardHidden|orientation|screenSize"
+             android:multiprocess="true">
         </activity>
 
-        <activity android:name="android.app.stubs.ActivityManagerMemoryClassLaunchActivity" />
+        <activity android:name="android.app.stubs.ActivityManagerMemoryClassLaunchActivity"/>
 
         <activity android:name="android.app.stubs.ActivityManagerMemoryClassTestActivity"
-                android:process=":memoryclass" />
+             android:process=":memoryclass"/>
 
         <activity android:name="android.app.stubs.PipNotSupportedActivity"
-                  android:label="PipNotSupportedActivity"
-                  android:resizeableActivity="true"
-                  android:supportsPictureInPicture="false"
-                  android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout">
+             android:label="PipNotSupportedActivity"
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="false"
+             android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.app.stubs.KeyboardShortcutsActivity" />
+        <activity android:name="android.app.stubs.KeyboardShortcutsActivity"/>
 
         <activity android:name="android.app.stubs.NewDocumentTestActivity"
-                  android:documentLaunchMode="intoExisting" />
+             android:documentLaunchMode="intoExisting"/>
 
         <activity android:name="android.app.stubs.DisplayTestActivity"
-            android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout" />
+             android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout"/>
 
         <activity android:name="android.app.stubs.ToolbarActivity"
-                  android:theme="@android:style/Theme.Material.Light.NoActionBar" />
+             android:theme="@android:style/Theme.Material.Light.NoActionBar"/>
 
-        <service
-            android:name="android.app.stubs.LiveWallpaper"
-            android:icon="@drawable/robot"
-            android:label="@string/wallpaper_title"
-            android:permission="android.permission.BIND_WALLPAPER">
+        <service android:name="android.app.stubs.LiveWallpaper"
+             android:icon="@drawable/robot"
+             android:label="@string/wallpaper_title"
+             android:permission="android.permission.BIND_WALLPAPER"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.service.wallpaper.WallpaperService">
                 </action>
             </intent-filter>
-            <meta-data
-                android:name="android.service.wallpaper"
-                android:resource="@xml/wallpaper">
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/wallpaper">
             </meta-data>
         </service>
 
         <service android:name="android.app.stubs.TestNotificationListener"
-                 android:exported="true"
-                 android:label="TestNotificationListener"
-                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+             android:exported="true"
+             android:label="TestNotificationListener"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.app.stubs.TestTileService"
-                 android:exported="true"
-                 android:label="TestTileService"
-                 android:icon="@drawable/robot"
-                 android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+             android:exported="true"
+             android:label="TestTileService"
+             android:icon="@drawable/robot"
+             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
             <intent-filter>
-                <action android:name="android.service.quicksettings.action.QS_TILE" />
+                <action android:name="android.service.quicksettings.action.QS_TILE"/>
             </intent-filter>
         </service>
 
         <service android:name="android.app.stubs.ToggleableTestTileService"
-                 android:exported="true"
-                 android:label="BooleanTestTileService"
-                 android:icon="@drawable/robot"
-                 android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+             android:exported="true"
+             android:label="BooleanTestTileService"
+             android:icon="@drawable/robot"
+             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
             <intent-filter>
-                <action android:name="android.service.quicksettings.action.QS_TILE" />
+                <action android:name="android.service.quicksettings.action.QS_TILE"/>
             </intent-filter>
             <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
-                       android:value="true"/>
+                 android:value="true"/>
         </service>
 
-        <activity android:name="android.app.stubs.AutomaticZenRuleActivity">
+        <activity android:name="android.app.stubs.AutomaticZenRuleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.action.AUTOMATIC_ZEN_RULE" />
+                <action android:name="android.app.action.AUTOMATIC_ZEN_RULE"/>
             </intent-filter>
             <meta-data android:name="android.service.zen.automatic.ruleType"
-                       android:value="@string/automatic_zen_rule_name" />
+                 android:value="@string/automatic_zen_rule_name"/>
             <meta-data android:name="android.service.zen.automatic.ruleInstanceLimit"
-                       android:value="2" />
+                 android:value="2"/>
         </activity>
 
         <receiver android:name="android.app.stubs.CommandReceiver"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name="android.app.stubs.SendBubbleActivity"
-                  android:turnScreenOn="true">
+             android:turnScreenOn="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.SEND" />
-                <data android:mimeType="text/plain" />
-                <data android:mimeType="image/*" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.SEND"/>
+                <data android:mimeType="text/plain"/>
+                <data android:mimeType="image/*"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.BubbledActivity"
-                  android:resizeableActivity="true"/>
+             android:resizeableActivity="true"/>
 
         <service android:name="android.app.stubs.BubblesTestService"
-                 android:label="BubblesTestsService"
-                 android:exported="true">
+             android:label="BubblesTestsService"
+             android:exported="true">
         </service>
 
-        <service android:name="android.app.stubs.LocalAlertService" />
+        <service android:name="android.app.stubs.LocalAlertService"/>
 
         <activity android:name=".SimpleActivity"
-                  android:excludeFromRecents="true">
+             android:excludeFromRecents="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
 
             <meta-data android:name="android.app.shortcuts"
-                       android:resource="@xml/shortcuts" />
+                 android:resource="@xml/shortcuts"/>
         </activity>
 
     </application>
 
 </manifest>
-
diff --git a/tests/app/app/src/android/app/stubs/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
index 5a13eab..03ad082 100644
--- a/tests/app/app/src/android/app/stubs/CommandReceiver.java
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.java
@@ -26,9 +26,12 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.util.ArrayMap;
 import android.util.Log;
 
+import java.util.concurrent.TimeUnit;
+
 public class CommandReceiver extends BroadcastReceiver {
 
     private static final String TAG = "CommandReceiver";
@@ -49,10 +52,20 @@
     public static final int COMMAND_CREATE_FGSL_PENDING_INTENT = 12;
     public static final int COMMAND_SEND_FGSL_PENDING_INTENT = 13;
     public static final int COMMAND_BIND_FOREGROUND_SERVICE = 14;
+    public static final int COMMAND_START_CHILD_PROCESS = 15;
+    public static final int COMMAND_STOP_CHILD_PROCESS = 16;
+    public static final int COMMAND_WAIT_FOR_CHILD_PROCESS_GONE = 17;
+
+    public static final int RESULT_CHILD_PROCESS_STARTED = IBinder.FIRST_CALL_TRANSACTION;
+    public static final int RESULT_CHILD_PROCESS_STOPPED = IBinder.FIRST_CALL_TRANSACTION + 1;
+    public static final int RESULT_CHILD_PROCESS_GONE = IBinder.FIRST_CALL_TRANSACTION + 2;
 
     public static final String EXTRA_COMMAND = "android.app.stubs.extra.COMMAND";
     public static final String EXTRA_TARGET_PACKAGE = "android.app.stubs.extra.TARGET_PACKAGE";
     public static final String EXTRA_FLAGS = "android.app.stubs.extra.FLAGS";
+    public static final String EXTRA_CALLBACK = "android.app.stubs.extra.callback";
+    public static final String EXTRA_CHILD_CMDLINE = "android.app.stubs.extra.child_cmdline";
+    public static final String EXTRA_TIMEOUT = "android.app.stubs.extra.child_cmdline";
 
     public static final String SERVICE_NAME = "android.app.stubs.LocalService";
     public static final String FG_SERVICE_NAME = "android.app.stubs.LocalForegroundService";
@@ -69,6 +82,9 @@
     // Map a packageName to a PendingIntent.
     private static ArrayMap<String, PendingIntent> sPendingIntent = new ArrayMap<>();
 
+    /** The child process, started via {@link #COMMAND_START_CHILD_PROCESS} */
+    private static Process sChildProcess;
+
     /**
      * Handle the different types of binding/unbinding requests.
      * @param context The Context in which the receiver is running.
@@ -124,6 +140,15 @@
             case COMMAND_BIND_FOREGROUND_SERVICE:
                 doBindService(context, intent, FG_LOCATION_SERVICE_NAME);
                 break;
+            case COMMAND_START_CHILD_PROCESS:
+                doStartChildProcess(context, intent);
+                break;
+            case COMMAND_STOP_CHILD_PROCESS:
+                doStopChildProcess(context, intent);
+                break;
+            case COMMAND_WAIT_FOR_CHILD_PROCESS_GONE:
+                doWaitForChildProcessGone(context, intent);
+                break;
         }
     }
 
@@ -224,6 +249,71 @@
         }
     }
 
+    private void doStartChildProcess(Context context, Intent intent) {
+        final Bundle extras = intent.getExtras();
+        final IBinder callback = extras.getBinder(EXTRA_CALLBACK);
+        final String[] cmdline = extras.getStringArray(EXTRA_CHILD_CMDLINE);
+        final Parcel data = Parcel.obtain();
+        final Parcel reply = Parcel.obtain();
+
+        try {
+            sChildProcess = Runtime.getRuntime().exec(cmdline);
+            if (sChildProcess != null) {
+                Log.i(TAG, "Forked child: " + sChildProcess);
+                callback.transact(RESULT_CHILD_PROCESS_STARTED, data, reply, 0);
+            } // else the remote will fail with timeout
+        } catch (Exception e) {
+            Log.e(TAG, "Unable to execute command", e);
+            sChildProcess = null;
+        } finally {
+            data.recycle();
+            reply.recycle();
+        }
+    }
+
+    private void doStopChildProcess(Context context, Intent intent) {
+        final Bundle extras = intent.getExtras();
+        final IBinder callback = extras.getBinder(EXTRA_CALLBACK);
+        final long timeout = extras.getLong(EXTRA_TIMEOUT);
+        waitForChildProcessGone(true, callback, RESULT_CHILD_PROCESS_STOPPED, timeout);
+    }
+
+    private void doWaitForChildProcessGone(Context context, Intent intent) {
+        final Bundle extras = intent.getExtras();
+        final IBinder callback = extras.getBinder(EXTRA_CALLBACK);
+        final long timeout = extras.getLong(EXTRA_TIMEOUT);
+        waitForChildProcessGone(false, callback, RESULT_CHILD_PROCESS_GONE, timeout);
+    }
+
+    private static synchronized void waitForChildProcessGone(final boolean destroy,
+            final IBinder callback, final int transactionCode, final long timeout) {
+        if (destroy) {
+            sChildProcess.destroy();
+        }
+        new Thread(() -> {
+            final Parcel data = Parcel.obtain();
+            final Parcel reply = Parcel.obtain();
+            try {
+                if (sChildProcess != null && sChildProcess.isAlive()) {
+                    final boolean exit = sChildProcess.waitFor(timeout, TimeUnit.MILLISECONDS);
+                    if (exit) {
+                        Log.i(TAG, "Child process died: " + sChildProcess);
+                        callback.transact(transactionCode, data, reply, 0);
+                    } else {
+                        Log.w(TAG, "Child process is still alive: " + sChildProcess);
+                    }
+                } else {
+                    callback.transact(transactionCode, data, reply, 0);
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Error", e);
+            } finally {
+                data.recycle();
+                reply.recycle();
+            }
+        }).start();
+    }
+
     private String getTargetPackage(Intent intent) {
         return intent.getStringExtra(EXTRA_TARGET_PACKAGE);
     }
diff --git a/tests/app/app/src/android/app/stubs/LocalAlertService.java b/tests/app/app/src/android/app/stubs/LocalAlertService.java
index 52dbc58..b800c5b 100644
--- a/tests/app/app/src/android/app/stubs/LocalAlertService.java
+++ b/tests/app/app/src/android/app/stubs/LocalAlertService.java
@@ -15,24 +15,22 @@
  */
 package android.app.stubs;
 
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
 import android.app.Service;
 import android.content.Intent;
 import android.graphics.Color;
 import android.graphics.Point;
-import android.os.Bundle;
 import android.os.IBinder;
-import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.TextView;
 
-import static android.view.Gravity.LEFT;
-import static android.view.Gravity.TOP;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-
 public class LocalAlertService extends Service {
     public static final String COMMAND_SHOW_ALERT = "show";
     public static final String COMMAND_HIDE_ALERT = "hide";
@@ -48,6 +46,7 @@
         } else if (COMMAND_HIDE_ALERT.equals(action)) {
             hideAlertWindow(mAlertWindow);
             mAlertWindow = null;
+            stopSelf();
         }
         return START_NOT_STICKY;
     }
diff --git a/tests/app/app/src/android/app/stubs/TestNotificationListener.java b/tests/app/app/src/android/app/stubs/TestNotificationListener.java
index a960403..14d5416 100644
--- a/tests/app/app/src/android/app/stubs/TestNotificationListener.java
+++ b/tests/app/app/src/android/app/stubs/TestNotificationListener.java
@@ -16,15 +16,16 @@
 package android.app.stubs;
 
 import android.content.ComponentName;
+import android.os.ConditionVariable;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
-import android.util.Log;
 
 import java.util.ArrayList;
 
 public class TestNotificationListener extends NotificationListenerService {
     public static final String TAG = "TestNotificationListener";
     public static final String PKG = "android.app.stubs";
+    private static final long CONNECTION_TIMEOUT_MS = 1000;
 
     private ArrayList<String> mTestPackages = new ArrayList<>();
 
@@ -32,6 +33,16 @@
     public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
     public RankingMap mRankingMap;
 
+    /**
+     * This controls whether there is a listener connected or not. Depending on the method, if the
+     * caller tries to use a listener after it has disconnected, NMS can throw a SecurityException.
+     *
+     * There is no race between onListenerConnected() and onListenerDisconnected() because they are
+     * called in the same thread. The value that getInstance() sees is guaranteed to be the value
+     * that was set by onListenerConnected() because of the happens-before established by the
+     * condition variable.
+     */
+    private static final ConditionVariable INSTANCE_AVAILABLE = new ConditionVariable(false);
     private static TestNotificationListener sNotificationListenerInstance = null;
     boolean isConnected;
 
@@ -55,16 +66,22 @@
     public void onListenerConnected() {
         super.onListenerConnected();
         sNotificationListenerInstance = this;
+        INSTANCE_AVAILABLE.open();
         isConnected = true;
     }
 
     @Override
     public void onListenerDisconnected() {
+        INSTANCE_AVAILABLE.close();
+        sNotificationListenerInstance = null;
         isConnected = false;
     }
 
     public static TestNotificationListener getInstance() {
-        return sNotificationListenerInstance;
+        if (INSTANCE_AVAILABLE.block(CONNECTION_TIMEOUT_MS)) {
+            return sNotificationListenerInstance;
+        }
+        return null;
     }
 
     public void resetData() {
@@ -72,6 +89,14 @@
         mRemoved.clear();
     }
 
+    public void addTestPackage(String packageName) {
+        mTestPackages.add(packageName);
+    }
+
+    public void removeTestPackage(String packageName) {
+        mTestPackages.remove(packageName);
+    }
+
     @Override
     public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
         if (sbn == null || !mTestPackages.contains(sbn.getPackageName())) { return; }
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index c982cd6..d6a731e 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -19,6 +19,10 @@
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.accessibilityservice.AccessibilityService;
+import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.app.cts.android.app.cts.tools.WaitForBroadcast;
 import android.app.cts.android.app.cts.tools.WatchUidRunner;
@@ -28,8 +32,11 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
+import android.provider.DeviceConfig;
 import android.test.InstrumentationTestCase;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 public class ActivityManagerFgsBgStartTest extends InstrumentationTestCase {
     private static final String TAG = ActivityManagerFgsBgStartTest.class.getName();
 
@@ -42,8 +49,15 @@
     private static final String ACTION_START_FGSL_RESULT =
             "android.app.stubs.LocalForegroundServiceLocation.RESULT";
 
+    private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED =
+            "default_fgs_starts_restriction_enabled";
+
     private static final int WAITFOR_MSEC = 10000;
 
+    private static final String[] PACKAGE_NAMES = {
+            PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, PACKAGE_NAME_APP3
+    };
+
     private Context mContext;
     private Instrumentation mInstrumentation;
 
@@ -55,6 +69,7 @@
         CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAME_APP1);
         CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAME_APP2);
         CtsAppTestUtils.turnScreenOn(mInstrumentation, mContext);
+        cleanupResiduals();
     }
 
     @Override
@@ -62,6 +77,21 @@
         super.tearDown();
         CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAME_APP1);
         CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAME_APP2);
+        cleanupResiduals();
+    }
+
+    private void cleanupResiduals() {
+        // Stop all the packages to avoid residual impact
+        final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+        for (int i = 0; i < PACKAGE_NAMES.length; i++) {
+            final String pkgName = PACKAGE_NAMES[i];
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                am.forceStopPackage(pkgName);
+            });
+        }
+        // Make sure we are in Home screen
+        mInstrumentation.getUiAutomation().performGlobalAction(
+                AccessibilityService.GLOBAL_ACTION_HOME);
     }
 
     /**
@@ -366,4 +396,147 @@
             uid1Watcher.finish();
         }
     }
+
+    /**
+     * Test FGS background startForeground() restriction.
+     * @throws Exception
+     */
+    public void testFgsStartFromBG() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        try {
+            // disable the FGS background startForeground() restriction.
+            enableFgsRestriction(false);
+            // Package1 is in BG state, Start FGS in package1.
+            Bundle bundle = new Bundle();
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
+            // Package1 is in STATE_FG_SERVICE.
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            // stop FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+
+            // Enable the FGS background startForeground() restriction.
+            enableFgsRestriction(true);
+            // Start FGS in BG state.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
+            // Package1 does not enter FGS state
+            try {
+                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                fail("Service should not enter foreground service state");
+            } catch (Exception e) {
+            }
+
+            // Put Package1 in TOP state.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+            // Now it can start FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
+            // Stop activity.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // FGS is still running.
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            // Stop the FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+            enableFgsRestriction(false);
+        }
+    }
+
+    public void testFgsStartFromBGWithBind() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+
+        try {
+            enableFgsRestriction(false);
+            // Package1 is in BG state, bind FGSL in package1 first.
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            Bundle bundle = new Bundle();
+            // Then start FGSL in package1
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
+            // Package1 is in FGS state
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+
+            // stop FGS
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // unbind service.
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+            enableFgsRestriction(false);
+        }
+    }
+
+    public void testFgsStartFromBGWithBindWithRestriction() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+
+        try {
+            enableFgsRestriction(true);
+            // Package1 is in BG state, bind FGSL in package1 first.
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // Then start FGSL in package1
+            Bundle bundle = new Bundle();
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
+            // Package1 does not enter FGS state
+            try {
+                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                fail("Service should not enter foreground service state");
+            } catch (Exception e) {
+            }
+
+            // stop FGS
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // unbind service.
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+            enableFgsRestriction(false);
+        }
+    }
+
+    private void enableFgsRestriction(boolean enable) {
+        runWithShellPermissionIdentity(()-> {
+                    DeviceConfig.setProperty("activity_manager",
+                            KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED, Boolean.toString(enable),
+                            false);
+                }
+        );
+    }
 }
diff --git a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
index 0de8c47..09eb585 100644
--- a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
@@ -104,6 +104,7 @@
     static final String ACTION_STOP_FOREGROUND = "com.android.test.action.STOP_FOREGROUND";
     static final String ACTION_START_THEN_FG = "com.android.test.action.START_THEN_FG";
     static final String ACTION_STOP_SERVICE = "com.android.test.action.STOP";
+    static final String ACTION_FINISH = "com.android.test.action.FINISH";
 
     private static final int TEMP_WHITELIST_DURATION_MS = 2000;
 
@@ -1331,11 +1332,12 @@
             device.waitForIdle();
 
             // Exit activity, check to see if we are now cached.
-            mInstrumentation.getUiAutomation().performGlobalAction(
-                    AccessibilityService.GLOBAL_ACTION_BACK);
-            // Hit back again in case the notification curtain is open
-            mInstrumentation.getUiAutomation().performGlobalAction(
-                    AccessibilityService.GLOBAL_ACTION_BACK);
+            final Intent finishIntent = new Intent();
+            finishIntent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
+            finishIntent.setAction(ACTION_FINISH);
+            finishIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            finishIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            mTargetContext.startActivity(finishIntent);
 
             // Wait for process to become cached
             uidCachedListener.waitForValue(
@@ -1496,14 +1498,17 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
             uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
 
-            // Exit activity, check to see if we are now cached.
             waitForAppFocus(CANT_SAVE_STATE_1_PACKAGE_NAME, WAIT_TIME);
             device.waitForIdle();
-            mInstrumentation.getUiAutomation().performGlobalAction(
-                    AccessibilityService.GLOBAL_ACTION_BACK);
-            // Hit back again in case the notification curtain is open
-            mInstrumentation.getUiAutomation().performGlobalAction(
-                    AccessibilityService.GLOBAL_ACTION_BACK);
+
+            // Exit activity, check to see if we are now cached.
+            final Intent finishIntent = new Intent();
+            finishIntent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
+            finishIntent.setAction(ACTION_FINISH);
+            finishIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            finishIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            mTargetContext.startActivity(finishIntent);
+
             uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
 
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index 60df0d9..84acb74 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -26,6 +26,7 @@
 import android.app.Instrumentation.ActivityMonitor;
 import android.app.Instrumentation.ActivityResult;
 import android.app.PendingIntent;
+import android.app.cts.android.app.cts.tools.WatchUidRunner;
 import android.app.stubs.ActivityManagerRecentOneActivity;
 import android.app.stubs.ActivityManagerRecentTwoActivity;
 import android.app.stubs.CommandReceiver;
@@ -36,20 +37,33 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.ConfigurationInfo;
 import android.content.res.Resources;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.platform.test.annotations.RestrictedBuildTest;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.server.wm.settings.SettingsSession;
 import android.support.test.uiautomator.UiDevice;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
+import androidx.test.filters.LargeTest;
+
 import com.android.compatibility.common.util.AmMonitor;
 import com.android.compatibility.common.util.SystemUtil;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
@@ -79,6 +93,8 @@
     private static final String ACTIVITY_TIME_TRACK_INFO = "com.android.cts.TIME_TRACK_INFO";
 
     private static final String PACKAGE_NAME_APP1 = "com.android.app1";
+    private static final String PACKAGE_NAME_APP2 = "com.android.app2";
+    private static final String PACKAGE_NAME_APP3 = "com.android.app3";
 
     private static final String MCC_TO_UPDATE = "987";
     private static final String MNC_TO_UPDATE = "654";
@@ -798,9 +814,9 @@
     public void testKillingPidsOnImperceptible() throws Exception {
         // Start remote service process
         final String remoteProcessName = STUB_PACKAGE_NAME + ":remote";
-        Intent intent = new Intent("android.app.REMOTESERVICE");
-        intent.setPackage(STUB_PACKAGE_NAME);
-        mTargetContext.startService(intent);
+        Intent remoteIntent = new Intent("android.app.REMOTESERVICE");
+        remoteIntent.setPackage(STUB_PACKAGE_NAME);
+        mTargetContext.startService(remoteIntent);
         Thread.sleep(WAITFOR_MSEC);
 
         RunningAppProcessInfo remote = getRunningAppProcessInfo(remoteProcessName);
@@ -813,7 +829,7 @@
             if (disabled) {
                 executeAndLogShellCommand("cmd deviceidle enable light");
             }
-            intent = new Intent(Intent.ACTION_MAIN);
+            final Intent intent = new Intent(Intent.ACTION_MAIN);
             intent.setClassName(SIMPLE_PACKAGE_NAME, SIMPLE_PACKAGE_NAME + SIMPLE_ACTIVITY);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             mTargetContext.startActivity(intent);
@@ -884,6 +900,7 @@
             triggerIdle(false);
             toggleScreenOn(true);
             appStartedReceiver.close();
+            mTargetContext.stopService(remoteIntent);
 
             if (disabled) {
                 executeAndLogShellCommand("cmd deviceidle disable light");
@@ -894,6 +911,333 @@
         }
     }
 
+    /**
+     * Verifies the system will kill app's child processes if they are using excessive cpu
+     */
+    @LargeTest
+    public void testKillingAppChildProcess() throws Exception {
+        final long powerCheckInterval = 5 * 1000;
+        final long processGoneTimeout = powerCheckInterval * 4;
+        final int waitForSec = 5 * 1000;
+        final String activityManagerConstants = "activity_manager_constants";
+
+        final SettingsSession<String> amSettings = new SettingsSession<>(
+                Settings.Global.getUriFor(activityManagerConstants),
+                Settings.Global::getString, Settings.Global::putString);
+
+        final ApplicationInfo ai = mTargetContext.getPackageManager()
+                .getApplicationInfo(PACKAGE_NAME_APP1, 0);
+        final WatchUidRunner watcher = new WatchUidRunner(mInstrumentation, ai.uid, waitForSec);
+
+        try {
+            // Shorten the power check intervals
+            amSettings.set("power_check_interval=" + powerCheckInterval);
+
+            // Make sure we could start activity from background
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist +" + PACKAGE_NAME_APP1);
+
+            // Start an activity
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            // Spawn a light weight child process
+            CountDownLatch startLatch = startChildProcessInPackage(PACKAGE_NAME_APP1,
+                    new String[] {"/system/bin/sh", "-c",  "sleep 1000"});
+
+            // Wait for the start of the child process
+            assertTrue("Failed to spawn child process",
+                    startLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Stop the activity
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+
+            // Wait for the system to kill that light weight child (it won't happen actually)
+            CountDownLatch stopLatch = initWaitingForChildProcessGone(
+                    PACKAGE_NAME_APP1, processGoneTimeout);
+
+            assertFalse("App's light weight child process shouldn't be gone",
+                    stopLatch.await(processGoneTimeout, TimeUnit.MILLISECONDS));
+
+            // Now kill the light weight child
+            stopLatch = stopChildProcess(PACKAGE_NAME_APP1, waitForSec);
+
+            assertTrue("Failed to kill app's light weight child process",
+                    stopLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Start an activity again
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            // Spawn the cpu intensive child process
+            startLatch = startChildProcessInPackage(PACKAGE_NAME_APP1,
+                    new String[] {"/system/bin/sh", "-c",  "while true; do :; done"});
+
+            // Wait for the start of the child process
+            assertTrue("Failed to spawn child process",
+                    startLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Stop the activity
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+
+            // Wait for the system to kill that heavy child due to excessive cpu usage,
+            // as well as the parent process.
+            watcher.waitFor(WatchUidRunner.CMD_GONE, processGoneTimeout);
+
+        } finally {
+            amSettings.close();
+
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist -" + PACKAGE_NAME_APP1);
+
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                // force stop test package, where the whole test process group will be killed.
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP1);
+            });
+
+            watcher.finish();
+        }
+    }
+
+
+    /**
+     * Verifies the system will trim app's child processes if there are too many
+     */
+    @LargeTest
+    public void testTrimAppChildProcess() throws Exception {
+        final long powerCheckInterval = 5 * 1000;
+        final long processGoneTimeout = powerCheckInterval * 4;
+        final int waitForSec = 5 * 1000;
+        final int maxPhantomProcessesNum = 2;
+        final String namespaceActivityManager = "activity_manager";
+        final String activityManagerConstants = "activity_manager_constants";
+        final String maxPhantomProcesses = "max_phantom_processes";
+
+        final SettingsSession<String> amSettings = new SettingsSession<>(
+                Settings.Global.getUriFor(activityManagerConstants),
+                Settings.Global::getString, Settings.Global::putString);
+        final Bundle currentMax = new Bundle();
+        final String keyCurrent = "current";
+
+        ApplicationInfo ai = mTargetContext.getPackageManager()
+                .getApplicationInfo(PACKAGE_NAME_APP1, 0);
+        final WatchUidRunner watcher1 = new WatchUidRunner(mInstrumentation, ai.uid, waitForSec);
+        ai = mTargetContext.getPackageManager().getApplicationInfo(PACKAGE_NAME_APP2, 0);
+        final WatchUidRunner watcher2 = new WatchUidRunner(mInstrumentation, ai.uid, waitForSec);
+        ai = mTargetContext.getPackageManager().getApplicationInfo(PACKAGE_NAME_APP3, 0);
+        final WatchUidRunner watcher3 = new WatchUidRunner(mInstrumentation, ai.uid, waitForSec);
+
+        try {
+            // Shorten the power check intervals
+            amSettings.set("power_check_interval=" + powerCheckInterval);
+
+            // Reduce the maximum phantom processes allowance
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                int current = DeviceConfig.getInt(namespaceActivityManager,
+                        maxPhantomProcesses, -1);
+                currentMax.putInt(keyCurrent, current);
+                DeviceConfig.setProperty(namespaceActivityManager,
+                        maxPhantomProcesses,
+                        Integer.toString(maxPhantomProcessesNum), false);
+            });
+
+            // Make sure we could start activity from background
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist +" + PACKAGE_NAME_APP1);
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist +" + PACKAGE_NAME_APP2);
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist +" + PACKAGE_NAME_APP3);
+
+            // Start an activity
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher1.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            // Spawn a light weight child process
+            CountDownLatch startLatch = startChildProcessInPackage(PACKAGE_NAME_APP1,
+                    new String[] {"/system/bin/sh", "-c",  "sleep 1000"});
+
+            // Wait for the start of the child process
+            assertTrue("Failed to spawn child process",
+                    startLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Start an activity in another package
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+
+            watcher2.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            // Spawn a light weight child process
+            startLatch = startChildProcessInPackage(PACKAGE_NAME_APP2,
+                    new String[] {"/system/bin/sh", "-c",  "sleep 1000"});
+
+            // Wait for the start of the child process
+            assertTrue("Failed to spawn child process",
+                    startLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Finish the 1st activity
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher1.waitFor(WatchUidRunner.CMD_CACHED, null);
+
+            // Wait for the system to kill that light weight child (it won't happen actually)
+            CountDownLatch stopLatch = initWaitingForChildProcessGone(
+                    PACKAGE_NAME_APP1, processGoneTimeout);
+
+            assertFalse("App's light weight child process shouldn't be gone",
+                    stopLatch.await(processGoneTimeout, TimeUnit.MILLISECONDS));
+
+            // Sleep a while
+            SystemClock.sleep(powerCheckInterval);
+
+            // Now start an activity in the 3rd party
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP3, 0, null);
+
+            watcher3.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            // Spawn a light weight child process
+            startLatch = startChildProcessInPackage(PACKAGE_NAME_APP3,
+                    new String[] {"/system/bin/sh", "-c",  "sleep 1000"});
+
+            // Wait for the start of the child process
+            assertTrue("Failed to spawn child process",
+                    startLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Now the 1st child process should have been gone.
+            stopLatch = initWaitingForChildProcessGone(
+                    PACKAGE_NAME_APP1, processGoneTimeout);
+
+            assertTrue("1st App's child process should have been gone",
+                    stopLatch.await(processGoneTimeout, TimeUnit.MILLISECONDS));
+
+        } finally {
+            amSettings.close();
+
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                final int current = currentMax.getInt(keyCurrent);
+                if (current < 0) {
+                    // Hm, DeviceConfig doesn't have an API to delete a property,
+                    // let's set it empty so the code will use the built-in default value.
+                    DeviceConfig.setProperty(namespaceActivityManager,
+                            maxPhantomProcesses, "", false);
+                } else {
+                    DeviceConfig.setProperty(namespaceActivityManager,
+                            maxPhantomProcesses, Integer.toString(current), false);
+                }
+            });
+
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist -" + PACKAGE_NAME_APP1);
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist -" + PACKAGE_NAME_APP2);
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist -" + PACKAGE_NAME_APP3);
+
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                // force stop test package, where the whole test process group will be killed.
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP1);
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP2);
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP3);
+            });
+
+            watcher1.finish();
+            watcher2.finish();
+            watcher3.finish();
+        }
+    }
+
+    private CountDownLatch startChildProcessInPackage(String pkgName, String[] cmdline) {
+        final CountDownLatch startLatch = new CountDownLatch(1);
+
+        final IBinder binder = new Binder() {
+            @Override
+            protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                    throws RemoteException {
+                switch (code) {
+                    case CommandReceiver.RESULT_CHILD_PROCESS_STARTED:
+                        startLatch.countDown();
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        };
+        final Bundle extras = new Bundle();
+        extras.putBinder(CommandReceiver.EXTRA_CALLBACK, binder);
+        extras.putStringArray(CommandReceiver.EXTRA_CHILD_CMDLINE, cmdline);
+
+        CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_CHILD_PROCESS,
+                pkgName, pkgName, 0, extras);
+
+        return startLatch;
+    }
+
+    final CountDownLatch stopChildProcess(String pkgName, long timeout) {
+        final CountDownLatch stopLatch = new CountDownLatch(1);
+
+        final IBinder binder = new Binder() {
+            @Override
+            protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                    throws RemoteException {
+                switch (code) {
+                    case CommandReceiver.RESULT_CHILD_PROCESS_STOPPED:
+                        stopLatch.countDown();
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        };
+        final Bundle extras = new Bundle();
+        extras.putBinder(CommandReceiver.EXTRA_CALLBACK, binder);
+        extras.putLong(CommandReceiver.EXTRA_TIMEOUT, timeout);
+
+        CommandReceiver.sendCommand(mTargetContext,
+                CommandReceiver.COMMAND_STOP_CHILD_PROCESS, pkgName, pkgName, 0, extras);
+
+        return stopLatch;
+    }
+
+    final CountDownLatch initWaitingForChildProcessGone(String pkgName, long timeout) {
+        final CountDownLatch stopLatch = new CountDownLatch(1);
+
+        final IBinder binder = new Binder() {
+            @Override
+            protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                    throws RemoteException {
+                switch (code) {
+                    case CommandReceiver.RESULT_CHILD_PROCESS_GONE:
+                        stopLatch.countDown();
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        };
+        final Bundle extras = new Bundle();
+        extras.putBinder(CommandReceiver.EXTRA_CALLBACK, binder);
+        extras.putLong(CommandReceiver.EXTRA_TIMEOUT, timeout);
+
+        CommandReceiver.sendCommand(mTargetContext,
+                CommandReceiver.COMMAND_WAIT_FOR_CHILD_PROCESS_GONE, pkgName, pkgName, 0, extras);
+
+        return stopLatch;
+    }
+
     private RunningAppProcessInfo getRunningAppProcessInfo(String processName) {
         try {
             return SystemUtil.callWithShellPermissionIdentity(()-> {
diff --git a/tests/app/src/android/app/cts/DownloadManagerTestBase.java b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
index fd9009a..3f6928f 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTestBase.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
@@ -18,6 +18,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -30,7 +31,9 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
+import android.net.ConnectivityManager;
 import android.net.Uri;
+import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
@@ -89,14 +92,19 @@
     protected Context mContext;
     protected DownloadManager mDownloadManager;
 
+    private WifiManager mWifiManager;
+    private ConnectivityManager mCm;
     private CtsTestServer mWebServer;
 
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
         mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
         mWebServer = new CtsTestServer(mContext);
         clearDownloads();
+        checkConnection();
     }
 
     @After
@@ -204,6 +212,23 @@
         return getFileData(uri, "_data");
     }
 
+    private void checkConnection() throws Exception {
+        if (!hasConnectedNetwork(mCm)) {
+            Log.d(TAG, "Enabling WiFi to ensure connectivity for this test");
+            runShellCommand("svc wifi enable");
+            runWithShellPermissionIdentity(mWifiManager::reconnect,
+                    android.Manifest.permission.NETWORK_SETTINGS);
+            final long startTime = SystemClock.elapsedRealtime();
+            while (!hasConnectedNetwork(mCm)
+                && (SystemClock.elapsedRealtime() - startTime) < SHORT_TIMEOUT) {
+                Thread.sleep(500);
+            }
+            if (!hasConnectedNetwork(mCm)) {
+                Log.d(TAG, "Unable to connect to any network");
+            }
+        }
+    }
+
     private static String getFileData(Uri uri, String projection) throws Exception {
         final Context context = InstrumentationRegistry.getTargetContext();
         final String[] projections =  new String[] { projection };
@@ -382,6 +407,10 @@
         }.run();
     }
 
+    private static boolean hasConnectedNetwork(final ConnectivityManager cm) {
+        return cm.getActiveNetwork() != null;
+    }
+
     protected void assertSuccessfulDownload(long id, File location) throws Exception {
         Cursor cursor = null;
         try {
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index bdbb0b7..c989bea 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -65,6 +65,7 @@
         assertNull(channel.getConversationId());
         assertNull(channel.getParentChannelId());
         assertFalse(channel.isImportantConversation());
+        assertFalse(channel.isDemoted());
     }
 
     public void testWriteToParcel() {
@@ -85,6 +86,7 @@
         channel.setBlockable(true);
         channel.setConversationId("parent_channel", "conversation 1");
         channel.setImportantConversation(true);
+        channel.setDemoted(true);
         Parcel parcel = Parcel.obtain();
         channel.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
@@ -231,4 +233,12 @@
 
         assertTrue(channel.hasUserSetSound());
     }
+
+    public void testIsDemoted() {
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        channel.setConversationId("parent", "conversation with friend");
+        channel.setDemoted(true);
+
+        assertTrue(channel.isDemoted());
+    }
 }
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 1cc0b6e..2276250 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -94,7 +94,12 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -111,6 +116,7 @@
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenPolicy;
 import android.test.AndroidTestCase;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.widget.RemoteViews;
@@ -136,6 +142,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -159,6 +166,22 @@
     private static final String SHARE_SHORTCUT_CATEGORY =
             "android.app.stubs.SHARE_SHORTCUT_CATEGORY";
 
+    private static final String TRAMPOLINE_APP =
+            "com.android.test.notificationtrampoline.current";
+    private static final String TRAMPOLINE_APP_API_30 =
+            "com.android.test.notificationtrampoline.api30";
+    private static final ComponentName TRAMPOLINE_SERVICE =
+            new ComponentName(TRAMPOLINE_APP,
+                    "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
+    private static final ComponentName TRAMPOLINE_SERVICE_API_30 =
+            new ComponentName(TRAMPOLINE_APP_API_30,
+                    "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
+
+    private static final long TIMEOUT_LONG_MS = 10000;
+    private static final long TIMEOUT_MS = 4000;
+    private static final int MESSAGE_BROADCAST_NOTIFICATION = 1;
+    private static final int MESSAGE_SERVICE_NOTIFICATION = 2;
+
     private PackageManager mPackageManager;
     private AudioManager mAudioManager;
     private NotificationManager mNotificationManager;
@@ -169,6 +192,7 @@
     private BroadcastReceiver mBubbleBroadcastReceiver;
     private boolean mBubblesEnabledSettingToRestore;
     private INotificationUriAccessService mNotificationUriAccessService;
+    private FutureServiceConnection mTrampolineConnection;
 
     @Override
     protected void setUp() throws Exception {
@@ -190,6 +214,9 @@
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mRuleIds = new ArrayList<>();
 
+        // ensure listener access isn't allowed before test runs (other tests could put
+        // TestListener in an unexpected state)
+        toggleListenerAccess(false);
         toggleNotificationPolicyAccess(mContext.getPackageName(),
                 InstrumentationRegistry.getInstrumentation(), true);
         mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL);
@@ -244,6 +271,16 @@
 
         // Restore bubbles setting
         setBubblesGlobal(mBubblesEnabledSettingToRestore);
+
+        // For trampoline tests
+        if (mTrampolineConnection != null) {
+            mContext.unbindService(mTrampolineConnection);
+            mTrampolineConnection = null;
+        }
+        if (mListener != null) {
+            mListener.removeTestPackage(TRAMPOLINE_APP_API_30);
+            mListener.removeTestPackage(TRAMPOLINE_APP);
+        }
     }
 
     private void assertNotificationCancelled(int id, boolean all) {
@@ -476,8 +513,8 @@
         try {
             toggleListenerAccess(true);
             mListener = TestNotificationListener.getInstance();
-            mListener.resetData();
             assertNotNull(mListener);
+            mListener.resetData();
         } catch (IOException e) {
         }
     }
@@ -613,6 +650,7 @@
         assertEquals(expected.getGroup(), actual.getGroup());
         assertEquals(expected.getConversationId(), actual.getConversationId());
         assertEquals(expected.getParentChannelId(), actual.getParentChannelId());
+        assertEquals(expected.isDemoted(), actual.isDemoted());
     }
 
     private void toggleNotificationPolicyAccess(String packageName,
@@ -624,8 +662,8 @@
 
         NotificationManager nm = mContext.getSystemService(NotificationManager.class);
         assertEquals("Notification Policy Access Grant is "
-                        + nm.isNotificationPolicyAccessGranted() + " not " + on, on,
-                nm.isNotificationPolicyAccessGranted());
+                + nm.isNotificationPolicyAccessGranted() + " not " + on + " for "
+                + packageName,  on, nm.isNotificationPolicyAccessGranted());
     }
 
     private void suspendPackage(String packageName,
@@ -857,6 +895,19 @@
         mContext.unregisterReceiver(mBubbleBroadcastReceiver);
     }
 
+    private void sendTrampolineMessage(ComponentName component, int message,
+            int notificationId, Handler callback) throws Exception {
+        if (mTrampolineConnection == null) {
+            Intent intent = new Intent();
+            intent.setComponent(component);
+            mTrampolineConnection = new FutureServiceConnection();
+            assertTrue(
+                    mContext.bindService(intent, mTrampolineConnection, Context.BIND_AUTO_CREATE));
+        }
+        Messenger service = new Messenger(mTrampolineConnection.get(TIMEOUT_MS));
+        service.send(Message.obtain(null, message, notificationId, -1, new Messenger(callback)));
+    }
+
     public void testConsolidatedNotificationPolicy() throws Exception {
         final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
         Policy origPolicy = mNotificationManager.getNotificationPolicy();
@@ -2096,7 +2147,7 @@
             mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
 
             // delay for streams to get into correct mute states
-            Thread.sleep(50);
+            Thread.sleep(1000);
             assertTrue("Music (media) stream should be muted",
                     mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
             assertTrue("System stream should be muted",
@@ -2105,8 +2156,9 @@
                     mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
 
             // Test requires that the phone's default state has no channels that can bypass dnd
-            assertTrue("Ringer stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+            // which we can't currently guarantee (b/169267379)
+            // assertTrue("Ringer stream should be muted",
+            //        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
         } finally {
             mNotificationManager.setInterruptionFilter(originalFilter);
             mNotificationManager.setNotificationPolicy(origPolicy);
@@ -2137,7 +2189,7 @@
             mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
 
             // delay for streams to get into correct mute states
-            Thread.sleep(50);
+            Thread.sleep(1000);
             assertFalse("Music (media) stream should not be muted",
                     mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
             assertTrue("System stream should be muted",
@@ -2146,8 +2198,9 @@
                     mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
 
             // Test requires that the phone's default state has no channels that can bypass dnd
-            assertTrue("Ringer stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+            // which we can't currently guarantee (b/169267379)
+            // assertTrue("Ringer stream should be muted",
+            //  mAudioManager.isStreamMute(AudioManager.STREAM_RING));
         } finally {
             mNotificationManager.setInterruptionFilter(originalFilter);
             mNotificationManager.setNotificationPolicy(origPolicy);
@@ -2855,8 +2908,6 @@
         }
     }
 
-    ;
-
     public void testNotificationUriPermissionsRevokedOnlyFromRemovedListeners() throws Exception {
         Uri background7Uri = Uri.parse(
                 "content://com.android.test.notificationprovider.provider/background7.png");
@@ -3571,8 +3622,7 @@
     }
 
     public void testOriginalChannelImportance() {
-        NotificationChannel channel = new NotificationChannel(
-                "my channel", "my channel", IMPORTANCE_HIGH);
+        NotificationChannel channel = new NotificationChannel(mId, "my channel", IMPORTANCE_HIGH);
 
         mNotificationManager.createNotificationChannel(channel);
 
@@ -3607,4 +3657,141 @@
         compareChannels(conversationChannel,
                 mNotificationManager.getNotificationChannel(channel.getId(), conversationId));
     }
+
+    public void testDemoteConversationChannel() {
+        final NotificationChannel channel =
+                new NotificationChannel(mId, "Messages", IMPORTANCE_DEFAULT);
+
+        String conversationId = "person a";
+
+        final NotificationChannel conversationChannel =
+                new NotificationChannel(mId + "child",
+                        "Messages from " + conversationId, IMPORTANCE_DEFAULT);
+        conversationChannel.setConversationId(channel.getId(), conversationId);
+
+        mNotificationManager.createNotificationChannel(channel);
+        mNotificationManager.createNotificationChannel(conversationChannel);
+
+        conversationChannel.setDemoted(true);
+
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                mNotificationManager.updateNotificationChannel(
+                        mContext.getPackageName(), android.os.Process.myUid(), channel));
+
+        assertEquals(false, mNotificationManager.getNotificationChannel(
+                channel.getId(), conversationId).isDemoted());
+    }
+
+    public void testActivityStartOnBroadcastTrampoline_isBlocked() throws Exception {
+        setUpNotifListener();
+        mListener.addTestPackage(TRAMPOLINE_APP);
+        EventCallback callback = new EventCallback();
+        int notificationId = 6001;
+
+        // Post notification and fire its pending intent
+        sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_BROADCAST_NOTIFICATION, notificationId,
+                callback);
+        StatusBarNotification statusBarNotification = findPostedNotification(notificationId, true);
+        assertNotNull("Notification not posted on time", statusBarNotification);
+        statusBarNotification.getNotification().contentIntent.send();
+
+        assertTrue("Broadcast not received on time",
+                callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
+        assertFalse("Activity start should have been blocked",
+                callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
+    }
+
+    public void testActivityStartOnServiceTrampoline_isBlocked() throws Exception {
+        setUpNotifListener();
+        mListener.addTestPackage(TRAMPOLINE_APP);
+        EventCallback callback = new EventCallback();
+        int notificationId = 6002;
+
+        // Post notification and fire its pending intent
+        sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_SERVICE_NOTIFICATION, notificationId,
+                callback);
+        StatusBarNotification statusBarNotification = findPostedNotification(notificationId, true);
+        assertNotNull("Notification not posted on time", statusBarNotification);
+        statusBarNotification.getNotification().contentIntent.send();
+
+        assertTrue("Service not started on time",
+                callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
+        assertFalse("Activity start should have been blocked",
+                callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
+    }
+
+    public void testActivityStartOnBroadcastTrampoline_whenApi30_isAllowed() throws Exception {
+        setUpNotifListener();
+        mListener.addTestPackage(TRAMPOLINE_APP_API_30);
+        EventCallback callback = new EventCallback();
+        int notificationId = 6003;
+
+        // Post notification and fire its pending intent
+        sendTrampolineMessage(TRAMPOLINE_SERVICE_API_30, MESSAGE_BROADCAST_NOTIFICATION,
+                notificationId, callback);
+        StatusBarNotification statusBarNotification = findPostedNotification(notificationId, true);
+        assertNotNull("Notification not posted on time", statusBarNotification);
+        statusBarNotification.getNotification().contentIntent.send();
+
+        assertTrue("Broadcast not received on time",
+                callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
+        assertTrue("Activity not started",
+                callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
+    }
+
+    public void testActivityStartOnServiceTrampoline_whenApi30_isAllowed() throws Exception {
+        setUpNotifListener();
+        mListener.addTestPackage(TRAMPOLINE_APP_API_30);
+        EventCallback callback = new EventCallback();
+        int notificationId = 6004;
+
+        // Post notification and fire its pending intent
+        sendTrampolineMessage(TRAMPOLINE_SERVICE_API_30, MESSAGE_SERVICE_NOTIFICATION,
+                notificationId, callback);
+        StatusBarNotification statusBarNotification = findPostedNotification(notificationId, true);
+        assertNotNull("Notification not posted on time", statusBarNotification);
+        statusBarNotification.getNotification().contentIntent.send();
+
+        assertTrue("Service not started on time",
+                callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
+        assertTrue("Activity not started",
+                callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
+    }
+
+    private static class FutureServiceConnection implements ServiceConnection {
+        public final CompletableFuture<IBinder> future = new CompletableFuture<>();
+        public IBinder get(long timeoutMs) throws Exception {
+            return future.get(timeoutMs, TimeUnit.MILLISECONDS);
+        }
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            future.complete(service);
+        }
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            fail(name + " disconnected");
+        }
+    }
+
+    private static class EventCallback extends Handler {
+        private static final int BROADCAST_RECEIVED = 1;
+        private static final int SERVICE_STARTED = 2;
+        private static final int ACTIVITY_STARTED = 3;
+
+        private final Map<Integer, ConditionVariable> mEvents =
+                Collections.synchronizedMap(new ArrayMap<>());
+
+        private EventCallback() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public void handleMessage(Message message) {
+            mEvents.computeIfAbsent(message.what, e -> new ConditionVariable()).open();
+        }
+
+        public boolean waitFor(int event, long timeoutMs) {
+            return mEvents.computeIfAbsent(event, e -> new ConditionVariable()).block(timeoutMs);
+        }
+    }
 }
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index b9f8784..994255f 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -290,10 +290,13 @@
         final Intent intent = new Intent();
         final PendingIntent actionIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
         mAction = null;
-        mAction = new Notification.Action.Builder(0, ACTION_TITLE, actionIntent).build();
+        mAction = new Notification.Action.Builder(0, ACTION_TITLE, actionIntent)
+                .setAuthenticationRequired(true)
+                .build();
         assertEquals(ACTION_TITLE, mAction.title);
         assertEquals(actionIntent, mAction.actionIntent);
         assertEquals(true, mAction.getAllowGeneratedReplies());
+        assertTrue(mAction.isAuthenticationRequired());
     }
 
     public void testNotification_addPerson() {
diff --git a/tests/app/src/android/app/cts/PendingIntentTest.java b/tests/app/src/android/app/cts/PendingIntentTest.java
index b0226c9..2582f28 100644
--- a/tests/app/src/android/app/cts/PendingIntentTest.java
+++ b/tests/app/src/android/app/cts/PendingIntentTest.java
@@ -32,7 +32,6 @@
 import android.os.Parcel;
 import android.os.SystemClock;
 import android.test.AndroidTestCase;
-import android.util.Log;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -143,7 +142,7 @@
         mIntent.setClass(mContext, PendingIntentStubActivity.class);
         mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         assertEquals(mContext.getPackageName(), mPendingIntent.getTargetPackage());
 
         mPendingIntent.send();
@@ -155,13 +154,21 @@
         // test getActivity return null
         mPendingIntent.cancel();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_NO_CREATE);
+                PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
         assertNull(mPendingIntent);
 
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
 
         pendingIntentSendError(mPendingIntent);
+
+        try {
+            mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
+                    PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_MUTABLE);
+            fail("Shouldn't accept both FLAG_IMMUTABLE and FLAG_MUTABLE for the PendingIntent");
+        } catch (IllegalArgumentException expected) {
+        }
+
     }
 
     private void pendingIntentSendError(PendingIntent pendingIntent) {
@@ -182,7 +189,7 @@
         mIntent = new Intent(MockReceiver.MOCKACTION);
         mIntent.setClass(mContext, MockReceiver.class);
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send();
 
@@ -192,13 +199,22 @@
         // test getBroadcast return null
         mPendingIntent.cancel();
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_NO_CREATE);
+                PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
         assertNull(mPendingIntent);
 
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
 
         pendingIntentSendError(mPendingIntent);
+
+        try {
+            mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                    PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_MUTABLE);
+            fail("Shouldn't accept both FLAG_IMMUTABLE and FLAG_MUTABLE for the PendingIntent");
+        } catch (IllegalArgumentException expected) {
+        }
+
+
     }
 
     // Local receiver for examining delivered broadcast intents
@@ -243,7 +259,7 @@
         Intent intent = new Intent(BROADCAST_ACTION);
         intent.putExtra(EXTRA_NAME, EXTRA_1);
 
-        pi = PendingIntent.getBroadcast(context, 0, intent, 0);
+        pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 
         try {
             br.reset();
@@ -256,7 +272,7 @@
 
             // Repeat PendingIntent.getBroadcast() *without* UPDATE_CURRENT, so we expect
             // the underlying Intent to still be the initial one with EXTRA_1
-            pi = PendingIntent.getBroadcast(context, 0, intent, 0);
+            pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
             br.reset();
             pi.send();
             assertTrue(br.waitForReceipt());
@@ -264,7 +280,8 @@
 
             // This time use UPDATE_CURRENT, and expect to get the updated extra when the
             // PendingIntent is sent
-            pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+            pi = PendingIntent.getBroadcast(context, 0, intent,
+                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
             br.reset();
             pi.send();
             assertTrue(br.waitForReceipt());
@@ -280,7 +297,7 @@
         mIntent = new Intent();
         mIntent.setClass(mContext, MockService.class);
         mPendingIntent = PendingIntent.getService(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send();
 
@@ -290,13 +307,21 @@
         // test getService return null
         mPendingIntent.cancel();
         mPendingIntent = PendingIntent.getService(mContext, 1, mIntent,
-                PendingIntent.FLAG_NO_CREATE);
+                PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
         assertNull(mPendingIntent);
 
         mPendingIntent = PendingIntent.getService(mContext, 1, mIntent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
 
         pendingIntentSendError(mPendingIntent);
+
+        try {
+            mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
+                    PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_MUTABLE);
+            fail("Shouldn't accept both FLAG_IMMUTABLE and FLAG_MUTABLE for the PendingIntent");
+        } catch (IllegalArgumentException expected) {
+        }
+
     }
 
     public void testStartServiceOnFinishedHandler() throws InterruptedException, CanceledException {
@@ -305,7 +330,7 @@
         mIntent = new Intent();
         mIntent.setClass(mContext, MockService.class);
         mPendingIntent = PendingIntent.getService(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send(mContext, 1, null, mFinish, null);
 
@@ -322,7 +347,7 @@
         mIntent = new Intent();
         mIntent.setClass(mContext, MockService.class);
         mPendingIntent = PendingIntent.getService(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send(mContext, 1, null, mFinish, mHandler);
 
@@ -340,7 +365,7 @@
         mIntent = new Intent();
         mIntent.setClass(mContext, MockService.class);
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send();
 
@@ -363,7 +388,7 @@
         mIntent.setAction(MockReceiver.MOCKACTION);
         mIntent.setClass(mContext, MockReceiver.class);
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send();
 
@@ -381,7 +406,7 @@
         mIntent = new Intent(MockReceiver.MOCKACTION);
         mIntent.setClass(mContext, MockReceiver.class);
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         // send result code 1.
         mPendingIntent.send(1);
@@ -413,7 +438,8 @@
 
         MockReceiver.prepareReceive(null, 0);
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send(mContext, 1, null);
         MockReceiver.waitForReceive(WAIT_TIME);
@@ -422,7 +448,8 @@
         assertEquals(1, MockReceiver.sResultCode);
         mPendingIntent.cancel();
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
 
         mPendingIntent.send(mContext, 2, mIntent);
@@ -437,7 +464,8 @@
         mIntent = new Intent(MockReceiver.MOCKACTION);
         mIntent.setClass(mContext, MockReceiver.class);
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
 
@@ -451,7 +479,8 @@
         assertEquals(1, MockReceiver.sResultCode);
         mPendingIntent.cancel();
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
 
@@ -467,7 +496,8 @@
 
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         mPendingIntent.send(3, mFinish, mHandler);
         waitForFinish(WAIT_TIME);
         assertTrue(mHandleResult);
@@ -485,7 +515,8 @@
         mIntent.setAction(MockReceiver.MOCKACTION);
         mIntent.setClass(getContext(), MockReceiver.class);
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
         mPendingIntent.send(mContext, 1, mIntent, null, null);
@@ -496,7 +527,8 @@
         assertEquals(MockReceiver.MOCKACTION, MockReceiver.sAction);
         mPendingIntent.cancel();
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
         mPendingIntent.send(mContext, 1, mIntent, mFinish, null);
@@ -507,7 +539,8 @@
         assertEquals(MockReceiver.MOCKACTION, MockReceiver.sAction);
         mPendingIntent.cancel();
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
         mPendingIntent.send(mContext, 1, mIntent, mFinish, mHandler);
@@ -528,7 +561,8 @@
         mIntent = new Intent(BAD_ACTION);
         mIntent.setAction(BAD_ACTION);
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
         mPendingIntent.send(mContext, 1, mIntent, mFinish, null);
@@ -539,7 +573,8 @@
         assertNull(MockReceiver.sAction);
         mPendingIntent.cancel();
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
         mPendingIntent.send(mContext, 1, mIntent, mFinish, mHandler);
@@ -554,32 +589,34 @@
     public void testGetTargetPackage() {
         mIntent = new Intent();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         assertEquals(mContext.getPackageName(), mPendingIntent.getTargetPackage());
     }
 
     public void testEquals() {
         mIntent = new Intent();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         PendingIntent target = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         assertFalse(mPendingIntent.equals(target));
         assertFalse(mPendingIntent.hashCode() == target.hashCode());
-        mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
 
-        target = PendingIntent.getActivity(mContext, 1, mIntent, 1);
+        target = PendingIntent.getActivity(mContext, 1, mIntent, 1 | PendingIntent.FLAG_IMMUTABLE);
         assertTrue(mPendingIntent.equals(target));
 
         mIntent = new Intent(MockReceiver.MOCKACTION);
-        target = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        target = PendingIntent.getBroadcast(mContext, 1, mIntent, 1 | PendingIntent.FLAG_IMMUTABLE);
         assertFalse(mPendingIntent.equals(target));
         assertFalse(mPendingIntent.hashCode() == target.hashCode());
 
-        mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent, 1);
-        target = PendingIntent.getActivity(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
+        target = PendingIntent.getActivity(mContext, 1, mIntent, 1 | PendingIntent.FLAG_IMMUTABLE);
 
         assertTrue(mPendingIntent.equals(target));
         assertEquals(mPendingIntent.hashCode(), target.hashCode());
@@ -588,7 +625,7 @@
     public void testDescribeContents() {
         mIntent = new Intent();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         final int expected = 0;
         assertEquals(expected, mPendingIntent.describeContents());
     }
@@ -596,7 +633,7 @@
     public void testWriteToParcel() {
         mIntent = new Intent();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         Parcel parcel = Parcel.obtain();
 
         mPendingIntent.writeToParcel(parcel, 0);
@@ -608,7 +645,7 @@
     public void testReadAndWritePendingIntentOrNullToParcel() {
         mIntent = new Intent();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         assertNotNull(mPendingIntent.toString());
 
         Parcel parcel = Parcel.obtain();
diff --git a/tests/app/src/android/app/cts/StatusBarManagerTest.java b/tests/app/src/android/app/cts/StatusBarManagerTest.java
index 241ee12..1866255 100644
--- a/tests/app/src/android/app/cts/StatusBarManagerTest.java
+++ b/tests/app/src/android/app/cts/StatusBarManagerTest.java
@@ -18,23 +18,21 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import android.Manifest;
 import android.app.StatusBarManager;
 import android.app.StatusBarManager.DisableInfo;
 import android.content.Context;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class StatusBarManagerTest {
@@ -99,7 +97,7 @@
 
     @Test
     public void testDisableForSimLock_setDisabledTrue() throws Exception {
-        mStatusBarManager.setDisabledForSimNetworkLock(true);
+        mStatusBarManager.setExpansionDisabledForSimNetworkLock(true);
 
         // Check for the default set of disable flags
         assertTrue(mStatusBarManager.getDisableInfo().isStatusBarExpansionDisabled());
@@ -108,8 +106,8 @@
     @Test
     public void testDisableForSimLock_setDisabledFalse() throws Exception {
         // First disable, then re-enable
-        mStatusBarManager.setDisabledForSimNetworkLock(true);
-        mStatusBarManager.setDisabledForSimNetworkLock(false);
+        mStatusBarManager.setExpansionDisabledForSimNetworkLock(true);
+        mStatusBarManager.setExpansionDisabledForSimNetworkLock(false);
 
         DisableInfo info = mStatusBarManager.getDisableInfo();
         assertTrue("Invalid disableFlags", info.areAllComponentsEnabled());
diff --git a/tests/app/src/android/app/cts/TileServiceTest.java b/tests/app/src/android/app/cts/TileServiceTest.java
index 152baa9..2509eb7 100644
--- a/tests/app/src/android/app/cts/TileServiceTest.java
+++ b/tests/app/src/android/app/cts/TileServiceTest.java
@@ -80,6 +80,16 @@
     }
 
     @Test
+    public void testTile_hasCorrectStateDescription() throws Exception {
+        initializeAndListen();
+
+        Tile tile = mTileService.getQsTile();
+        tile.setStateDescription("test_stateDescription");
+        tile.updateTile();
+        assertEquals("test_stateDescription", tile.getStateDescription());
+    }
+
+    @Test
     public void testShowDialog() throws Exception {
         Looper.prepare();
         Dialog dialog = new AlertDialog.Builder(mContext).create();
diff --git a/tests/appintegrity/AndroidManifest.xml b/tests/appintegrity/AndroidManifest.xml
index e0a8816..c7ad8b5 100644
--- a/tests/appintegrity/AndroidManifest.xml
+++ b/tests/appintegrity/AndroidManifest.xml
@@ -15,23 +15,23 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.appintegrity.cts">
+     package="android.appintegrity.cts">
 
     <uses-sdk android:targetSdkVersion="30"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="CtsAppIntegrityDeviceActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="CtsAppIntegrityDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.appintegrity.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.appintegrity.cts">
     </instrumentation>
 </manifest>
diff --git a/tests/apppredictionservice/AndroidManifest.xml b/tests/apppredictionservice/AndroidManifest.xml
index 8ee464a..1c77832 100644
--- a/tests/apppredictionservice/AndroidManifest.xml
+++ b/tests/apppredictionservice/AndroidManifest.xml
@@ -14,31 +14,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
 -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.apppredictionservice.cts"
-    android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.apppredictionservice.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- TODO(b/111701043): Update with required permissions -->
-        <service
-            android:name=".PredictionService"
-            android:label="CtsAppPredictionService">
+        <service android:name=".PredictionService"
+             android:label="CtsAppPredictionService"
+             android:exported="true">
             <intent-filter>
                 <!-- This constant must match AppPredictionService.SERVICE_INTERFACE -->
-                <action android:name="android.service.appprediction.AppPredictionService" />
+                <action android:name="android.service.appprediction.AppPredictionService"/>
             </intent-filter>
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the App Prediction Framework APIs."
-        android:targetPackage="android.apppredictionservice.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the App Prediction Framework APIs."
+         android:targetPackage="android.apppredictionservice.cts">
     </instrumentation>
 
 </manifest>
diff --git a/tests/appsearch/Android.bp b/tests/appsearch/Android.bp
new file mode 100644
index 0000000..2bb810d
--- /dev/null
+++ b/tests/appsearch/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsAppSearchTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "testng",
+    ],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "vts",
+        "vts10",
+        "general-tests",
+    ],
+    platform_apis: true,
+}
diff --git a/tests/appsearch/AndroidManifest.xml b/tests/appsearch/AndroidManifest.xml
new file mode 100644
index 0000000..7eac46b
--- /dev/null
+++ b/tests/appsearch/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.appsearch" >
+    <application android:label="CtsAppSearchTestCases">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.appsearch"
+                     android:label="CtsAppSearchTestCases"/>
+</manifest>
diff --git a/tests/appsearch/AndroidTest.xml b/tests/appsearch/AndroidTest.xml
new file mode 100644
index 0000000..a86e339
--- /dev/null
+++ b/tests/appsearch/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Config for CTS AppSearch test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsAppSearchTestCases.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.cts.appsearch" />
+    </test>
+</configuration>
diff --git a/tests/appsearch/OWNERS b/tests/appsearch/OWNERS
new file mode 100644
index 0000000..cf3ad8a
--- /dev/null
+++ b/tests/appsearch/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 755061
+adorokhine@google.com
diff --git a/tests/appsearch/TEST_MAPPING b/tests/appsearch/TEST_MAPPING
new file mode 100644
index 0000000..f728da5
--- /dev/null
+++ b/tests/appsearch/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppSearchTestCases"
+    }
+  ]
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java
new file mode 100644
index 0000000..1d9aaad
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.cts.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchDocument;
+import android.app.appsearch.AppSearchEmail;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.SearchResults;
+import android.app.appsearch.SearchSpec;
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.collect.ImmutableList;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class AppSearchManagerTest {
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    private final AppSearchManager mAppSearch = mContext.getSystemService(AppSearchManager.class);
+
+    @Before
+    public void setUp() {
+        // Remove all documents from any instances that may have been created in the tests.
+        checkIsSuccess(mAppSearch.setSchema(ImmutableList.of(), /*forceOverride=*/ true));
+    }
+
+    @Test
+    public void testGetService() {
+        assertThat(mContext.getSystemService(Context.APP_SEARCH_SERVICE)).isNotNull();
+        assertThat(mContext.getSystemService(AppSearchManager.class)).isNotNull();
+        assertThat(mAppSearch).isNotNull();
+    }
+
+    @Test
+    public void testSetSchema() {
+        AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
+                .addProperty(new AppSearchSchema.PropertyConfig.Builder("subject")
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).addProperty(new AppSearchSchema.PropertyConfig.Builder("body")
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+        checkIsSuccess(mAppSearch.setSchema(emailSchema));
+    }
+
+    @Test
+    public void testPutDocuments() {
+        // Schema registration
+        checkIsSuccess(mAppSearch.setSchema(AppSearchEmail.SCHEMA));
+
+        // Index a document
+        AppSearchEmail email = new AppSearchEmail.Builder("uri1")
+                .setFrom("from@example.com")
+                .setTo("to1@example.com", "to2@example.com")
+                .setSubject("testPut example")
+                .setBody("This is the body of the testPut email")
+                .build();
+
+        AppSearchBatchResult result = mAppSearch.putDocuments(ImmutableList.of(email));
+        checkIsSuccess(result);
+        assertThat(result.getSuccesses()).containsExactly("uri1", null);
+        assertThat(result.getFailures()).isEmpty();
+    }
+
+    @Test
+    public void testGetDocuments() {
+        // Schema registration
+        checkIsSuccess(mAppSearch.setSchema(AppSearchEmail.SCHEMA));
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsSuccess(mAppSearch.putDocuments(ImmutableList.of(inEmail)));
+
+        // Get the document
+        List<AppSearchDocument> outDocuments = doGet("uri1");
+        assertThat(outDocuments).hasSize(1);
+        AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
+        assertThat(outEmail).isEqualTo(inEmail);
+    }
+
+    @Test
+    public void testQuery() {
+        // Schema registration
+        checkIsSuccess(mAppSearch.setSchema(AppSearchEmail.SCHEMA));
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsSuccess(mAppSearch.putDocuments(ImmutableList.of(inEmail)));
+
+        // Query for the document
+        List<AppSearchDocument> results = doQuery("body");
+        assertThat(results).hasSize(1);
+        assertThat(results.get(0)).isEqualTo(inEmail);
+
+        // Multi-term query
+        results = doQuery("body email");
+        assertThat(results).hasSize(1);
+        assertThat(results.get(0)).isEqualTo(inEmail);
+    }
+
+    @Test
+    public void testQuery_TypeFilter() {
+        // Schema registration
+        AppSearchSchema genericSchema = new AppSearchSchema.Builder("Generic")
+                .addProperty(new PropertyConfig.Builder("foo")
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+                ).build();
+        checkIsSuccess(mAppSearch.setSchema(AppSearchEmail.SCHEMA, genericSchema));
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchDocument inDoc = new AppSearchDocument.Builder<>("uri2", "Generic")
+                .setProperty("foo", "body").build();
+        checkIsSuccess(mAppSearch.putDocuments(ImmutableList.of(inEmail, inDoc)));
+
+        // Query for the documents
+        List<AppSearchDocument> results = doQuery("body");
+        assertThat(results).hasSize(2);
+        assertThat(results).containsExactly(inEmail, inDoc);
+
+        // Query only for Document
+        results = doQuery("body", "Generic");
+        assertThat(results).hasSize(1);
+        assertThat(results).containsExactly(inDoc);
+    }
+
+    @Test
+    public void testDelete() {
+        // Schema registration
+        checkIsSuccess(mAppSearch.setSchema(AppSearchEmail.SCHEMA));
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsSuccess(mAppSearch.putDocuments(ImmutableList.of(email1, email2)));
+
+        // Check the presence of the documents
+        assertThat(doGet("uri1")).hasSize(1);
+        assertThat(doGet("uri2")).hasSize(1);
+
+        // Delete the document
+        checkIsSuccess(mAppSearch.delete(ImmutableList.of("uri1")));
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, AppSearchDocument> getResult =
+                mAppSearch.getDocuments(ImmutableList.of("uri1", "uri2"));
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+    }
+
+    @Test
+    public void testRemoveByTypes() {
+        // Schema registration
+        AppSearchSchema genericSchema = new AppSearchSchema.Builder("Generic").build();
+        checkIsSuccess(mAppSearch.setSchema(
+                ImmutableList.of(AppSearchEmail.SCHEMA, genericSchema), /*forceOverride=*/ false));
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        AppSearchDocument document1 =
+                new AppSearchDocument.Builder<>("uri3", "Generic").build();
+        checkIsSuccess(mAppSearch.putDocuments(ImmutableList.of(email1, email2, document1)));
+
+        // Check the presence of the documents
+        assertThat(doGet("uri1", "uri2", "uri3")).hasSize(3);
+
+        // Delete the email type
+        checkIsSuccess(mAppSearch.deleteByTypes(ImmutableList.of(AppSearchEmail.SCHEMA_TYPE)));
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, AppSearchDocument> getResult =
+                mAppSearch.getDocuments(ImmutableList.of("uri1", "uri2", "uri3"));
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getFailures().get("uri2").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getSuccesses().get("uri3")).isEqualTo(document1);
+    }
+
+    private List<AppSearchDocument> doGet(String... uris) {
+        AppSearchBatchResult<String, AppSearchDocument> result =
+                mAppSearch.getDocuments(Arrays.asList(uris));
+        checkIsSuccess(result);
+        assertThat(result.getSuccesses()).hasSize(uris.length);
+        assertThat(result.getFailures()).isEmpty();
+        List<AppSearchDocument> list = new ArrayList<>(uris.length);
+        for (String uri : uris) {
+            list.add(result.getSuccesses().get(uri));
+        }
+        return list;
+    }
+
+    private List<AppSearchDocument> doQuery(String queryExpression, String... schemaTypes) {
+        AppSearchResult<SearchResults> result = mAppSearch.query(
+                queryExpression,
+                new SearchSpec.Builder()
+                        .setSchemaTypes(schemaTypes)
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .build());
+        checkIsSuccess(result);
+        SearchResults searchResults = result.getResultValue();
+        List<AppSearchDocument> documents = new ArrayList<>();
+        while (searchResults.hasNext()) {
+            documents.add(searchResults.next().getDocument());
+        }
+        return documents;
+    }
+
+    private void checkIsSuccess(AppSearchResult<?> result) {
+        if (!result.isSuccess()) {
+            throw new AssertionFailedError("AppSearchResult not successful: " + result);
+        }
+    }
+
+    private void checkIsSuccess(AppSearchBatchResult<?,?> result) {
+        if (!result.isSuccess()) {
+            throw new AssertionFailedError(
+                    "AppSearchBatchResult not successful: " + result.getFailures());
+        }
+    }
+}
diff --git a/tests/aslr/TEST_MAPPING b/tests/aslr/TEST_MAPPING
new file mode 100644
index 0000000..ee44915
--- /dev/null
+++ b/tests/aslr/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAslrMallocTestCases"
+    }
+  ]
+}
diff --git a/tests/attentionservice/AndroidManifest.xml b/tests/attentionservice/AndroidManifest.xml
index 22ab937..341a6f9 100644
--- a/tests/attentionservice/AndroidManifest.xml
+++ b/tests/attentionservice/AndroidManifest.xml
@@ -16,30 +16,30 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.attentionservice.cts">
+     package="android.attentionservice.cts">
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <service
-            android:name=".CtsTestAttentionService"
-            android:label="CtsTestAttentionService"
-            android:permission="android.permission.BIND_ATTENTION_SERVICE">
+        <uses-library android:name="android.test.runner"/>
+        <service android:name=".CtsTestAttentionService"
+             android:label="CtsTestAttentionService"
+             android:permission="android.permission.BIND_ATTENTION_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.service.attention.AttentionService"/>
             </intent-filter>
         </service>
-        <activity android:name="CtsAttentionServiceDeviceActivity" >
+        <activity android:name="CtsAttentionServiceDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the Attention Service APIs."
-        android:targetPackage="android.attentionservice.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the Attention Service APIs."
+         android:targetPackage="android.attentionservice.cts">
     </instrumentation>
 </manifest>
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index f690678..28d4ebd 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -14,202 +14,210 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.autofillservice.cts"
-    android:targetSandboxVersion="2">
+     package="android.autofillservice.cts"
+     android:targetSandboxVersion="2">
 
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
     <application>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name=".LoginActivity" >
+        <activity android:name=".activities.LoginActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".PreFilledLoginActivity" />
-        <activity android:name=".LoginWithCustomHighlightActivity"
-                  android:theme="@style/MyAutofilledHighlight"/>
-        <activity android:name=".LoginWithStringsActivity" />
-        <activity android:name=".LoginNotImportantForAutofillActivity" />
-        <activity android:name=".LoginNotImportantForAutofillWrappedActivityContextActivity" />
-        <activity android:name=".LoginNotImportantForAutofillWrappedApplicationContextActivity" />
-        <activity android:name=".WelcomeActivity" android:taskAffinity=".WelcomeActivity"/>
-        <activity android:name=".ViewActionActivity"
-                  android:taskAffinity=".ViewActionActivity"
-                  android:launchMode="singleTask">
+        <activity android:name=".activities.PreFilledLoginActivity"/>
+        <activity android:name=".activities.LoginWithCustomHighlightActivity"
+             android:theme="@style/MyAutofilledHighlight"/>
+        <activity android:name=".activities.LoginWithStringsActivity"/>
+        <activity android:name=".activities.LoginNotImportantForAutofillActivity"/>
+        <activity android:name=".activities.LoginNotImportantForAutofillWrappedActivityContextActivity"/>
+        <activity android:name=".activities.LoginNotImportantForAutofillWrappedApplicationContextActivity"/>
+        <activity android:name=".activities.WelcomeActivity"
+             android:taskAffinity=".WelcomeActivity"/>
+        <activity android:name=".activities.ViewActionActivity"
+             android:taskAffinity=".ViewActionActivity"
+             android:launchMode="singleTask"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <data android:scheme="autofillcts" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.VIEW"/>
+                <data android:scheme="autofillcts"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity android:name=".SecondActivity" android:taskAffinity=".SecondActivity"/>
-        <activity android:name=".ViewAttributesTestActivity" />
-        <activity android:name=".AuthenticationActivity" />
-        <activity android:name=".ManualAuthenticationActivity" />
-        <activity android:name=".CheckoutActivity" android:taskAffinity=".CheckoutActivity"/>
-        <activity android:name=".InitializedCheckoutActivity" />
-        <activity android:name=".DatePickerCalendarActivity" />
-        <activity android:name=".DatePickerSpinnerActivity" />
-        <activity android:name=".TimePickerClockActivity" />
-        <activity android:name=".TimePickerSpinnerActivity" />
-        <activity android:name=".FatActivity" />
-        <activity android:name=".VirtualContainerActivity">
+        <activity android:name=".activities.SecondActivity"
+             android:taskAffinity=".SecondActivity"/>
+        <activity android:name=".activities.ViewAttributesTestActivity"/>
+        <activity android:name=".activities.AuthenticationActivity"/>
+        <activity android:name=".activities.ManualAuthenticationActivity"/>
+        <activity android:name=".activities.CheckoutActivity"
+             android:taskAffinity=".CheckoutActivity"/>
+        <activity android:name=".activities.InitializedCheckoutActivity"/>
+        <activity android:name=".activities.DatePickerCalendarActivity"/>
+        <activity android:name=".activities.DatePickerSpinnerActivity"/>
+        <activity android:name=".activities.TimePickerClockActivity"/>
+        <activity android:name=".activities.TimePickerSpinnerActivity"/>
+        <activity android:name=".activities.FatActivity"/>
+        <activity android:name=".activities.VirtualContainerActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".OptionalSaveActivity" />
-        <activity android:name=".AllAutofillableViewsActivity" />
-        <activity android:name=".GridActivity"/>
-        <activity android:name=".EmptyActivity"/>
-        <activity android:name=".DummyActivity"/>
-        <activity android:name=".OutOfProcessLoginActivity"
-            android:process="android.autofillservice.cts.outside"/>
-        <activity android:name=".FragmentContainerActivity" />
-        <activity android:name=".DuplicateIdActivity"
-            android:theme="@android:style/Theme.NoTitleBar" />
-        <activity android:name=".SimpleSaveActivity"/>
-        <activity android:name=".PreSimpleSaveActivity">
+        <activity android:name=".activities.OptionalSaveActivity"/>
+        <activity android:name=".activities.GridActivity"/>
+        <activity android:name=".activities.EmptyActivity"/>
+        <activity android:name=".activities.DummyActivity"/>
+        <activity android:name=".activities.OutOfProcessLoginActivity"
+             android:process="android.autofillservice.cts.outside"/>
+        <activity android:name=".activities.FragmentContainerActivity"/>
+        <activity android:name=".activities.DuplicateIdActivity"
+             android:theme="@android:style/Theme.NoTitleBar"/>
+        <activity android:name=".activities.SimpleSaveActivity"/>
+        <activity android:name=".activities.PreSimpleSaveActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".WebViewActivity"/>
-        <activity android:name=".WebViewMultiScreenLoginActivity"/>
-        <activity android:name=".TrampolineWelcomeActivity"/>
-        <activity android:name=".AttachedContextActivity"/>
-        <activity android:name=".DialogLauncherActivity" >
+        <activity android:name=".activities.WebViewActivity"/>
+        <activity android:name=".activities.WebViewMultiScreenLoginActivity"/>
+        <activity android:name=".activities.TrampolineWelcomeActivity"/>
+        <activity android:name=".activities.AttachedContextActivity"/>
+        <activity android:name=".activities.DialogLauncherActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".MultiWindowLoginActivity" />
-        <activity android:name=".MultiWindowEmptyActivity"
-            android:taskAffinity="nobody.but.EmptyActivity"
-            android:exported="true" />
+        <activity android:name=".activities.MultiWindowLoginActivity"/>
+        <activity android:name=".activities.MultiWindowEmptyActivity"
+             android:taskAffinity="nobody.but.EmptyActivity"
+             android:exported="true"/>
 
-        <activity android:name=".TrampolineForResultActivity" />
-        <activity android:name=".OnCreateServiceStatusVerifierActivity"/>
-        <activity android:name=".UsernameOnlyActivity" >
+        <activity android:name=".activities.TrampolineForResultActivity"/>
+        <activity android:name=".activities.OnCreateServiceStatusVerifierActivity"/>
+        <activity android:name=".activities.UsernameOnlyActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".PasswordOnlyActivity" >
+        <activity android:name=".activities.PasswordOnlyActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".augmented.AugmentedLoginActivity">
+        <activity android:name=".activities.AugmentedLoginActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".augmented.AugmentedAuthActivity" />
-        <activity android:name=".SimpleAfterLoginActivity" />
-        <activity android:name=".SimpleBeforeLoginActivity" />
-        <activity android:name=".NonAutofillableActivity" />
+        <activity android:name=".activities.AugmentedAuthActivity" />
+        <activity android:name=".activities.SimpleAfterLoginActivity"/>
+        <activity android:name=".activities.SimpleBeforeLoginActivity"/>
+        <activity android:name=".activities.NonAutofillableActivity"/>
 
-        <receiver android:name=".SelfDestructReceiver"
-            android:exported="true"
-            android:process="android.autofillservice.cts.outside"/>
-        <receiver android:name=".OutOfProcessLoginActivityFinisherReceiver"
-            android:exported="true"
-            android:process="android.autofillservice.cts.outside"/>
+        <receiver android:name=".testcore.SelfDestructReceiver"
+             android:exported="true"
+             android:process="android.autofillservice.cts.outside"/>
+        <receiver android:name=".testcore.OutOfProcessLoginActivityFinisherReceiver"
+             android:exported="true"
+             android:process="android.autofillservice.cts.outside"/>
 
-        <service
-            android:name=".InstrumentedAutoFillService"
-            android:label="InstrumentedAutoFillService"
-            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+        <service android:name=".testcore.InstrumentedAutoFillService"
+             android:label="InstrumentedAutoFillService"
+             android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
         </service>
-        <service
-            android:name=".InstrumentedAutoFillServiceCompatMode"
-            android:label="InstrumentedAutoFillServiceCompatMode"
-            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+        <service android:name=".testcore.InstrumentedAutoFillServiceCompatMode"
+             android:label="testcore.InstrumentedAutoFillServiceCompatMode"
+             android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
-            <meta-data
-                android:name="android.autofill"
-                android:resource="@xml/autofill_service_compat_mode_config">
+            <meta-data android:name="android.autofill"
+                 android:resource="@xml/autofill_service_compat_mode_config">
             </meta-data>
         </service>
-        <service
-            android:name=".inline.InstrumentedAutoFillServiceInlineEnabled"
-            android:label="InstrumentedAutoFillServiceInlineEnabled"
-            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+        <service android:name=".testcore.InstrumentedAutoFillServiceInlineEnabled"
+             android:label="InstrumentedAutoFillServiceInlineEnabled"
+             android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
-            <meta-data
-                android:name="android.autofill"
-                android:resource="@xml/autofill_service_inline_enabled">
+            <meta-data android:name="android.autofill"
+                 android:resource="@xml/autofill_service_inline_enabled">
             </meta-data>
         </service>
-        <service
-            android:name=".NoOpAutofillService"
-            android:label="NoOpAutofillService"
-            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+        <service android:name=".testcore.NoOpAutofillService"
+             android:label="NoOpAutofillService"
+             android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
         </service>
         <!--  BadAutofillService does not declare the proper permission -->
-        <service
-            android:name=".BadAutofillService"
-            android:label="BadAutofillService">
+        <service android:name=".testcore.BadAutofillService"
+             android:label="testcore.BadAutofillService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
         </service>
 
-        <service
-            android:name=".augmented.CtsAugmentedAutofillService"
-            android:label="CtsAugmentedAutofillService"
-            android:permission="android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE" >
+        <service android:name=".testcore.CtsAugmentedAutofillService"
+             android:label="CtsAugmentedAutofillService"
+             android:permission="android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the AutoFill Framework APIs."
-        android:targetPackage="android.autofillservice.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the AutoFill Framework APIs."
+         android:targetPackage="android.autofillservice.cts">
     </instrumentation>
 
 </manifest>
diff --git a/tests/autofillservice/AndroidTest.xml b/tests/autofillservice/AndroidTest.xml
index 288ddba..7aec376 100644
--- a/tests/autofillservice/AndroidTest.xml
+++ b/tests/autofillservice/AndroidTest.xml
@@ -23,6 +23,12 @@
   <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
     <option name="cleanup-apks" value="true" />
     <option name="test-file-name" value="CtsAutoFillServiceTestCases.apk" />
+    <option name="test-file-name" value="TestAutofillServiceApp.apk" />
+  </target_preparer>
+
+  <!-- Load additional APKs onto device -->
+  <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+    <option name="push" value="TestAutofillServiceApp.apk->/data/local/tmp/cts/autofill/TestAutofillServiceApp.apk" />
   </target_preparer>
 
   <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
@@ -41,6 +47,11 @@
     <option name="teardown-command" value="cmd autofill set bind-instant-service-allowed false" />
   </target_preparer>
 
+  <!--  Remove the pushed APK after test is done. -->
+  <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+    <option name="teardown-command" value="rm -rf /data/local/tmp/cts/autofill" />
+  </target_preparer>
+
   <test class="com.android.tradefed.testtype.AndroidJUnitTest">
     <option name="package" value="android.autofillservice.cts" />
     <!-- 20x default timeout of 600sec -->
diff --git a/tests/autofillservice/TestAutofillService/Android.bp b/tests/autofillservice/TestAutofillService/Android.bp
new file mode 100644
index 0000000..2204f27
--- /dev/null
+++ b/tests/autofillservice/TestAutofillService/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "TestAutofillServiceApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/tests/autofillservice/TestAutofillService/AndroidManifest.xml b/tests/autofillservice/TestAutofillService/AndroidManifest.xml
new file mode 100644
index 0000000..bcd391e
--- /dev/null
+++ b/tests/autofillservice/TestAutofillService/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.autofill.cts2"
+          android:targetSandboxVersion="2">
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+
+        <activity android:name=".QueryAutofillStatusActivity"
+                  android:label="QueryAutofillStatusActivity"
+                  android:taskAffinity=".QueryAutofillStatusActivity"
+                  android:theme="@android:style/Theme.NoTitleBar"
+                  android:exported="true">
+        </activity>
+        <service android:name=".NoOpAutofillService"
+                 android:label="NoOpAutofillService"
+                 android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.service.autofill.AutofillService"/>
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:label="CTS tests for the AutoFill Framework APIs."
+                     android:targetPackage="android.autofill.cts2">
+    </instrumentation>
+
+</manifest>
diff --git a/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/NoOpAutofillService.java b/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/NoOpAutofillService.java
new file mode 100644
index 0000000..eaf69b0
--- /dev/null
+++ b/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/NoOpAutofillService.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofill.cts2;
+
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.util.Log;
+
+/**
+ * {@link AutofillService} implementation that does not do anything.
+ */
+public class NoOpAutofillService extends AutofillService {
+
+    private static final String TAG = "NoOpAutofillService";
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+        Log.d(TAG, "onFillRequest()");
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        Log.d(TAG, "onFillResponse()");
+    }
+}
diff --git a/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/QueryAutofillStatusActivity.java b/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/QueryAutofillStatusActivity.java
new file mode 100644
index 0000000..e0d9e2b
--- /dev/null
+++ b/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/QueryAutofillStatusActivity.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofill.cts2;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+/**
+ * An activity that queries its AutofillService status when started and immediately terminates
+ * itself.
+ */
+public class QueryAutofillStatusActivity extends Activity {
+
+    private static final String TAG = "QueryAutofillServiceStatusActivity";
+
+    // Autofill enable status, the value should be the same with AutofillManagerTest in
+    // CtsAutofillServiceTestCases.
+    private static final int AUTOFILL_ENABLE = 1;
+    private static final int AUTOFILL_DISABLE = 2;
+
+    private PendingIntent mPendingIntent;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Intent intent = getIntent();
+        mPendingIntent = intent.getParcelableExtra("finishBroadcast");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        hasEnabledAutofillServicesAndFinish();
+    }
+
+    private void hasEnabledAutofillServicesAndFinish() {
+        // Check if the calling application provides a AutofillService that is enabled
+        final AutofillManager afm = getSystemService(AutofillManager.class);
+        final boolean enabled = afm.hasEnabledAutofillServices();
+        Log.w(TAG, "hasEnabledAutofillServices()= " + enabled);
+
+        if (mPendingIntent != null) {
+            try {
+                final int resultCode = enabled ? AUTOFILL_ENABLE : AUTOFILL_DISABLE;
+                mPendingIntent.send(resultCode);
+            } catch (CanceledException e) {
+                Log.w(TAG, "Pending intent " + mPendingIntent + " canceled");
+            }
+        }
+        finish();
+    }
+}
diff --git a/tests/autofillservice/res/drawable/my_drawable.xml b/tests/autofillservice/res/drawable/my_drawable.xml
index eb1b15a..62433d7 100644
--- a/tests/autofillservice/res/drawable/my_drawable.xml
+++ b/tests/autofillservice/res/drawable/my_drawable.xml
@@ -15,4 +15,5 @@
   * limitations under the License.
   -->
 
-<android.autofillservice.cts.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"/>
+<android.autofillservice.cts.testcore.MyDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/tests/autofillservice/res/layout/all_autofill_able_views_activity.xml b/tests/autofillservice/res/layout/all_autofill_able_views_activity.xml
deleted file mode 100644
index 6920f19..0000000
--- a/tests/autofillservice/res/layout/all_autofill_able_views_activity.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <EditText android:id="@+id/editText" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:visibility="gone"/>
-
-    <CheckBox android:id="@+id/compoundButton" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:visibility="gone" />
-
-    <RadioGroup android:id="@+id/radioGroup" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:visibility="gone">
-
-        <RadioButton android:id="@+id/radioButton1" android:layout_width="wrap_content"
-            android:layout_height="wrap_content" android:checked="true"/>
-
-        <RadioButton android:id="@+id/radioButton2" android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-
-    </RadioGroup>
-
-    <Spinner android:id="@+id/spinner" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:entries="@array/cc_expiration_values"
-        android:visibility="gone" />
-
-    <DatePicker android:id="@+id/datePicker" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:visibility="gone" />
-
-    <TimePicker android:id="@+id/timePicker" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:visibility="gone" />
-
-</LinearLayout>
diff --git a/tests/autofillservice/res/layout/checkout_activity.xml b/tests/autofillservice/res/layout/checkout_activity.xml
index 4197e43..83f5585 100644
--- a/tests/autofillservice/res/layout/checkout_activity.xml
+++ b/tests/autofillservice/res/layout/checkout_activity.xml
@@ -112,4 +112,24 @@
             android:text="Buy it" />
     </LinearLayout>
 
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <DatePicker android:id="@+id/datePicker"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TimePicker android:id="@+id/timePicker"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+    </LinearLayout>
+
 </LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/fat_activity.xml b/tests/autofillservice/res/layout/fat_activity.xml
index 2efd33c..8a66004 100644
--- a/tests/autofillservice/res/layout/fat_activity.xml
+++ b/tests/autofillservice/res/layout/fat_activity.xml
@@ -137,7 +137,7 @@
 
     </LinearLayout>
 
-    <view class="android.autofillservice.cts.FatActivity$MyView"
+    <view class="android.autofillservice.cts.activities.FatActivity$MyView"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:autofillHints="importantAmI">
diff --git a/tests/autofillservice/res/layout/virtual_container_activity.xml b/tests/autofillservice/res/layout/virtual_container_activity.xml
index e105d22..f4e9b85 100644
--- a/tests/autofillservice/res/layout/virtual_container_activity.xml
+++ b/tests/autofillservice/res/layout/virtual_container_activity.xml
@@ -44,7 +44,8 @@
             android:layout_height="wrap_content" />
     </LinearLayout>
 
-    <android.autofillservice.cts.VirtualContainerView xmlns:android="http://schemas.android.com/apk/res/android"
+    <android.autofillservice.cts.activities.VirtualContainerView
+        xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/virtual_container_view"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
diff --git a/tests/autofillservice/res/layout/webview_only_activity.xml b/tests/autofillservice/res/layout/webview_only_activity.xml
index 469028a..e78a22d 100644
--- a/tests/autofillservice/res/layout/webview_only_activity.xml
+++ b/tests/autofillservice/res/layout/webview_only_activity.xml
@@ -24,7 +24,7 @@
     android:focusableInTouchMode="true"
     android:orientation="vertical" >
 
-    <android.autofillservice.cts.MyWebView
+    <android.autofillservice.cts.activities.MyWebView
         android:id="@+id/my_webview"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
deleted file mode 100644
index 6884f06..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.Activity;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.view.PixelCopy;
-import android.view.View;
-import android.view.autofill.AutofillManager;
-
-import androidx.annotation.NonNull;
-
-import com.android.compatibility.common.util.RetryableException;
-import com.android.compatibility.common.util.SynchronousPixelCopy;
-import com.android.compatibility.common.util.Timeout;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
-  * Base class for all activities in this test suite
-  */
-public abstract class AbstractAutoFillActivity extends Activity {
-
-    private final CountDownLatch mDestroyedLatch = new CountDownLatch(1);
-    protected final String mTag = getClass().getSimpleName();
-    private MyAutofillCallback mCallback;
-
-    /**
-     * Run an action in the UI thread, and blocks caller until the action is finished.
-     */
-    public final void syncRunOnUiThread(Runnable action) {
-        syncRunOnUiThread(action, Timeouts.UI_TIMEOUT.ms());
-    }
-
-    /**
-     * Run an action in the UI thread, and blocks caller until the action is finished or it times
-     * out.
-     */
-    public final void syncRunOnUiThread(Runnable action, long timeoutMs) {
-        final CountDownLatch latch = new CountDownLatch(1);
-        runOnUiThread(() -> {
-            action.run();
-            latch.countDown();
-        });
-        try {
-            if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
-                throw new RetryableException("action on UI thread timed out after %d ms",
-                        timeoutMs);
-            }
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new RuntimeException("Interrupted", e);
-        }
-    }
-
-    public AutofillManager getAutofillManager() {
-        return getSystemService(AutofillManager.class);
-    }
-
-    /**
-     * Takes a screenshot from the whole activity.
-     *
-     * <p><b>Note:</b> this screenshot only contains the contents of the activity, it doesn't
-     * include the autofill UIs; if you need to check that, please use
-     * {@link UiBot#takeScreenshot()} instead.
-     */
-    public Bitmap takeScreenshot() {
-        return takeScreenshot(findViewById(android.R.id.content).getRootView());
-    }
-
-    /**
-     * Takes a screenshot from the a view.
-     */
-    public Bitmap takeScreenshot(View view) {
-        final Rect srcRect = new Rect();
-        syncRunOnUiThread(() -> view.getGlobalVisibleRect(srcRect));
-        final Bitmap dest = Bitmap.createBitmap(
-                srcRect.width(), srcRect.height(), Bitmap.Config.ARGB_8888);
-
-        final SynchronousPixelCopy copy = new SynchronousPixelCopy();
-        final int copyResult = copy.request(getWindow(), srcRect, dest);
-        assertThat(copyResult).isEqualTo(PixelCopy.SUCCESS);
-
-        return dest;
-    }
-
-    /**
-     * Registers and returns a custom callback for autofill events.
-     *
-     * <p>Note: caller doesn't need to call {@link #unregisterCallback()}, it will be automatically
-     * unregistered on {@link #finish()}.
-     */
-    public MyAutofillCallback registerCallback() {
-        assertWithMessage("already registered").that(mCallback).isNull();
-        mCallback = new MyAutofillCallback();
-        getAutofillManager().registerCallback(mCallback);
-        return mCallback;
-    }
-
-    /**
-     * Unregister the callback from the {@link AutofillManager}.
-     *
-     * <p>This method just neeed to be called when a test case wants to explicitly test the behavior
-     * of the activity when the callback is unregistered.
-     */
-    protected void unregisterCallback() {
-        assertWithMessage("not registered").that(mCallback).isNotNull();
-        unregisterNonNullCallback();
-    }
-
-    /**
-     * Waits until {@link #onDestroy()} is called.
-     */
-    public void waintUntilDestroyed(@NonNull Timeout timeout) throws InterruptedException {
-        if (!mDestroyedLatch.await(timeout.ms(), TimeUnit.MILLISECONDS)) {
-            throw new RetryableException(timeout, "activity %s not destroyed", this);
-        }
-    }
-
-    private void unregisterNonNullCallback() {
-        getAutofillManager().unregisterCallback(mCallback);
-        mCallback = null;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        AutofillTestWatcher.registerActivity("onCreate()", this);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        // Activitiy is typically unregistered at finish(), but we need to unregister here too
-        // for the cases where it's destroyed due to a config change (like device rotation).
-        AutofillTestWatcher.unregisterActivity("onDestroy()", this);
-        mDestroyedLatch.countDown();
-    }
-
-    @Override
-    public void finish() {
-        finishOnly();
-        AutofillTestWatcher.unregisterActivity("finish()", this);
-    }
-
-    /**
-     * Finishes the activity, without unregistering it from {@link AutofillTestWatcher}.
-     */
-    void finishOnly() {
-        if (mCallback != null) {
-            unregisterNonNullCallback();
-        }
-        super.finish();
-    }
-
-    /**
-     * Clears focus from input fields.
-     */
-    public void clearFocus() {
-        throw new UnsupportedOperationException("Not implemented by " + getClass());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java
deleted file mode 100644
index 154db78..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.Button;
-import android.widget.DatePicker;
-import android.widget.EditText;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Base class for an activity that has the following fields:
- *
- * <ul>
- *   <li>A DatePicker (id: date_picker)
- *   <li>An EditText that is filled with the DatePicker when it changes (id: output)
- *   <li>An OK button that finishes it and navigates to the {@link WelcomeActivity}
- * </ul>
- *
- * <p>It's abstract because the sub-class must provide the view id, so it can support multiple
- * UI types (like calendar and spinner).
- */
-abstract class AbstractDatePickerActivity extends AbstractAutoFillActivity {
-
-    private static final long OK_TIMEOUT_MS = 1000;
-
-    static final String ID_DATE_PICKER = "date_picker";
-    static final String ID_OUTPUT = "output";
-
-    private DatePicker mDatePicker;
-    private EditText mOutput;
-    private Button mOk;
-
-    private FillExpectation mExpectation;
-    private CountDownLatch mOkLatch;
-
-    protected abstract int getContentView();
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(getContentView());
-
-        mDatePicker = (DatePicker) findViewById(R.id.date_picker);
-
-        mDatePicker.setOnDateChangedListener((v, y, m, d) -> updateOutputWithDate(y, m, d));
-
-        mOutput = (EditText) findViewById(R.id.output);
-        mOk = (Button) findViewById(R.id.ok);
-        mOk.setOnClickListener((v) -> ok());
-    }
-
-    public DatePicker getDatePicker() {
-        return mDatePicker;
-    }
-
-    private void updateOutputWithDate(int year, int month, int day) {
-        final String date = year + "/" + month + "/" + day;
-        mOutput.setText(date);
-    }
-
-    private void ok() {
-        final Intent intent = new Intent(this, WelcomeActivity.class);
-        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Good news everyone! The world didn't end!");
-        startActivity(intent);
-        if (mOkLatch != null) {
-            // Latch is not set when activity launched outside tests
-            mOkLatch.countDown();
-        }
-        finish();
-    }
-
-    /**
-     * Sets the expectation for an auto-fill request, so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    void expectAutoFill(String output, int year, int month, int day) {
-        mExpectation = new FillExpectation(output, year, month, day);
-        mOutput.addTextChangedListener(mExpectation.outputWatcher);
-        mDatePicker.setOnDateChangedListener((v, y, m, d) -> {
-            updateOutputWithDate(y, m, d);
-            mExpectation.dateListener.onDateChanged(v, y, m, d);
-        });
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, int, int, int)}.
-     */
-    void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        mExpectation.outputWatcher.assertAutoFilled();
-        mExpectation.dateListener.assertAutoFilled();
-    }
-
-    /**
-     * Visits the {@code output} in the UiThread.
-     */
-    void onOutput(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mOutput));
-    }
-
-    /**
-     * Sets the date in the {@link DatePicker}.
-     */
-    void setDate(int year, int month, int day) {
-        syncRunOnUiThread(() -> mDatePicker.updateDate(year, month, day));
-    }
-
-    /**
-     * Taps the ok button in the UI thread.
-     */
-    void tapOk() throws Exception {
-        mOkLatch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> mOk.performClick());
-        boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) waiting for OK action", OK_TIMEOUT_MS)
-                .that(called).isTrue();
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final MultipleTimesTextWatcher outputWatcher;
-        private final OneTimeDateListener dateListener;
-
-        private FillExpectation(String output, int year, int month, int day) {
-            // Output is called twice: by the DateChangeListener and by auto-fill.
-            outputWatcher = new MultipleTimesTextWatcher("output", 2, mOutput, output);
-            dateListener = new OneTimeDateListener("datePicker", mDatePicker, year, month, day);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractGridActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractGridActivityTestCase.java
deleted file mode 100644
index 6e7b475..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractGridActivityTestCase.java
+++ /dev/null
@@ -1,70 +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.autofillservice.cts;
-
-import android.view.accessibility.AccessibilityEvent;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Base class for test cases using {@link GridActivity}.
- */
-abstract class AbstractGridActivityTestCase
-        extends AutoFillServiceTestCase.AutoActivityLaunch<GridActivity> {
-
-    protected GridActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<GridActivity> getActivityRule() {
-        return new AutofillActivityTestRule<GridActivity>(GridActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-                postActivityLaunched();
-            }
-        };
-    }
-
-    /**
-     * Hook for subclass to customize activity after it's launched.
-     */
-    protected void postActivityLaunched() {
-    }
-
-    /**
-     * Focus to a cell and expect window event
-     */
-    protected void focusCell(int row, int column) throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column));
-    }
-
-    /**
-     * Focus to a cell and expect no window event.
-     */
-    protected void focusCellNoWindowChange(int row, int column) {
-        final AccessibilityEvent event;
-        try {
-            event = mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column),
-                    Timeouts.WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS);
-        } catch (WindowChangeTimeoutException ex) {
-            // no window events! looking good
-            return;
-        }
-        throw new IllegalStateException(String.format("Expect no window event when focusing to"
-                + " column %d row %d, but event happened: %s", row, column, event));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
deleted file mode 100644
index 8def8d4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
+++ /dev/null
@@ -1,86 +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.autofillservice.cts;
-
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Base class for test cases using {@link LoginActivity}.
- */
-public abstract class AbstractLoginActivityTestCase
-        extends AutoFillServiceTestCase.AutoActivityLaunch<LoginActivity> {
-
-    protected LoginActivity mActivity;
-
-    protected AbstractLoginActivityTestCase() {
-    }
-
-    protected AbstractLoginActivityTestCase(UiBot inlineUiBot) {
-        super(inlineUiBot);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<LoginActivity> getActivityRule() {
-        return new AutofillActivityTestRule<LoginActivity>(
-                LoginActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    /**
-     * Requests focus on username and expect Window event happens.
-     */
-    protected void requestFocusOnUsername() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus));
-    }
-
-    /**
-     * Requests focus on username and expect no Window event happens.
-     */
-    protected void requestFocusOnUsernameNoWindowChange() {
-        final AccessibilityEvent event;
-        try {
-            event = mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus),
-                    Timeouts.WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS);
-        } catch (WindowChangeTimeoutException ex) {
-            // no window events! looking good
-            return;
-        }
-        throw new IllegalStateException("Expect no window event when focusing to"
-                + " username, but event happened: " + event);
-    }
-
-    /**
-     * Requests focus on password and expect Window event happens.
-     */
-    protected void requestFocusOnPassword() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.onPassword(View::requestFocus));
-    }
-
-    /**
-     * Clears focus from input fields by focusing on the parent layout.
-     */
-    protected void clearFocus() {
-        mActivity.clearFocus();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java
deleted file mode 100644
index a997590..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TimePicker;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Base class for an activity that has the following fields:
- *
- * <ul>
- *   <li>A TimePicker (id: date_picker)
- *   <li>An EditText that is filled with the TimePicker when it changes (id: output)
- *   <li>An OK button that finishes it and navigates to the {@link WelcomeActivity}
- * </ul>
- *
- * <p>It's abstract because the sub-class must provide the view id, so it can support multiple
- * UI types (like clock and spinner).
- */
-abstract class AbstractTimePickerActivity extends AbstractAutoFillActivity {
-
-    private static final long OK_TIMEOUT_MS = 1000;
-
-    static final String ID_TIME_PICKER = "time_picker";
-    static final String ID_OUTPUT = "output";
-
-    private TimePicker mTimePicker;
-    private EditText mOutput;
-    private Button mOk;
-
-    private FillExpectation mExpectation;
-    private CountDownLatch mOkLatch;
-
-    protected abstract int getContentView();
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(getContentView());
-
-        mTimePicker = (TimePicker) findViewById(R.id.time_picker);
-
-        mTimePicker.setOnTimeChangedListener((v, m, h) -> updateOutputWithTime(m, h));
-
-        mOutput = (EditText) findViewById(R.id.output);
-        mOk = (Button) findViewById(R.id.ok);
-        mOk.setOnClickListener((v) -> ok());
-    }
-
-    private void updateOutputWithTime(int hour, int minute) {
-        final String time = hour + ":" + minute;
-        mOutput.setText(time);
-    }
-
-    private void ok() {
-        final Intent intent = new Intent(this, WelcomeActivity.class);
-        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "It's Adventure Time!");
-        startActivity(intent);
-        if (mOkLatch != null) {
-            // Latch is not set when activity launched outside tests
-            mOkLatch.countDown();
-        }
-        finish();
-    }
-
-    /**
-     * Sets the expectation for an auto-fill request, so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    void expectAutoFill(String output, int hour, int minute) {
-        mExpectation = new FillExpectation(output, hour, minute);
-        mOutput.addTextChangedListener(mExpectation.outputWatcher);
-        mTimePicker.setOnTimeChangedListener((v, h, m) -> {
-            updateOutputWithTime(h, m);
-            mExpectation.timeListener.onTimeChanged(v, h, m);
-        });
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, int, int)}.
-     */
-    void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        mExpectation.timeListener.assertAutoFilled();
-        mExpectation.outputWatcher.assertAutoFilled();
-    }
-
-    /**
-     * Visits the {@code output} in the UiThread.
-     */
-    void onOutput(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mOutput));
-    }
-
-    /**
-     * Sets the time in the {@link TimePicker}.
-     */
-    void setTime(int hour, int minute) {
-        syncRunOnUiThread(() -> {
-            mTimePicker.setHour(hour);
-            mTimePicker.setMinute(minute);
-        });
-    }
-
-    /**
-     * Taps the ok button in the UI thread.
-     */
-    void tapOk() throws Exception {
-        mOkLatch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> mOk.performClick());
-        boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) waiting for OK action", OK_TIMEOUT_MS)
-                .that(called).isTrue();
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final MultipleTimesTextWatcher outputWatcher;
-        private final MultipleTimesTimeListener timeListener;
-
-        private FillExpectation(String output, int hour, int minute) {
-            // Output is called twice: by the TimeChangeListener and by auto-fill.
-            outputWatcher = new MultipleTimesTextWatcher("output", 2, mOutput, output);
-            timeListener = new MultipleTimesTimeListener("timePicker", 1, mTimePicker, hour,
-                    minute);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java
deleted file mode 100644
index ce11da8..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java
+++ /dev/null
@@ -1,60 +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.autofillservice.cts;
-
-import android.os.SystemClock;
-import android.support.test.uiautomator.UiObject2;
-import android.view.KeyEvent;
-import android.widget.EditText;
-
-abstract class AbstractWebViewActivity extends AbstractAutoFillActivity {
-
-    public static final String FAKE_DOMAIN = "y.u.no.real.server";
-
-    public static final String HTML_NAME_USERNAME = "username";
-    public static final String HTML_NAME_PASSWORD = "password";
-
-    protected MyWebView mWebView;
-
-    protected UiObject2 getInput(UiBot uiBot, UiObject2 label) throws Exception {
-        // Then the input is next.
-        final UiObject2 parent = label.getParent();
-        UiObject2 previous = null;
-        for (UiObject2 child : parent.getChildren()) {
-            if (label.equals(previous)) {
-                if (child.getClassName().equals(EditText.class.getName())) {
-                    return child;
-                }
-                uiBot.dumpScreen("getInput() for " + child + "failed");
-                throw new IllegalStateException("Invalid class for " + child);
-            }
-            previous = child;
-        }
-        uiBot.dumpScreen("getInput() for label " + label + "failed");
-        throw new IllegalStateException("could not find username (label=" + label + ")");
-    }
-
-    public void dispatchKeyPress(int keyCode) {
-        runOnUiThread(() -> {
-            KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
-            mWebView.dispatchKeyEvent(keyEvent);
-            keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
-            mWebView.dispatchKeyEvent(keyEvent);
-        });
-        // wait webview to process the key event.
-        SystemClock.sleep(300);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java
deleted file mode 100644
index c189d85..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java
+++ /dev/null
@@ -1,44 +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.autofillservice.cts;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-public abstract class AbstractWebViewTestCase<A extends AbstractWebViewActivity>
-        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
-
-    protected AbstractWebViewTestCase() {
-    }
-
-    protected AbstractWebViewTestCase(UiBot inlineUiBot) {
-        super(inlineUiBot);
-    }
-
-    // TODO(b/64951517): WebView currently does not trigger the autofill callbacks when values are
-    // set using accessibility.
-    protected static final boolean INJECT_EVENTS = true;
-
-    @BeforeClass
-    public static void setReplierMode() {
-        sReplier.setIdMode(IdMode.HTML_NAME);
-    }
-
-    @AfterClass
-    public static void resetReplierMode() {
-        sReplier.setIdMode(IdMode.RESOURCE_ID);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AllAutofillableViewsActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AllAutofillableViewsActivity.java
deleted file mode 100644
index 10cc322..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AllAutofillableViewsActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-
-public class AllAutofillableViewsActivity extends AbstractAutoFillActivity {
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.all_autofill_able_views_activity);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java
deleted file mode 100644
index af713d3..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.widget.EditText;
-
-import java.util.regex.Pattern;
-
-/**
- * A {@link TextWatcher} that appends pound signs ({@code #} at the beginning and end of the text.
- */
-public final class AntiTrimmerTextWatcher implements TextWatcher {
-
-    /**
-     * Regex used to revert a String that was "anti-trimmed".
-     */
-    public static final Pattern TRIMMER_PATTERN = Pattern.compile("#(.*)#");
-
-    private final EditText mView;
-
-    public AntiTrimmerTextWatcher(EditText view) {
-        mView = view;
-        mView.addTextChangedListener(this);
-    }
-
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-        mView.removeTextChangedListener(this);
-        mView.setText("#" + s + "#");
-    }
-
-    @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-    }
-
-    @Override
-    public void afterTextChanged(Editable s) {
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivity.java
deleted file mode 100644
index 097967e..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivity.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.widget.EditText;
-
-import java.util.Locale;
-
-/**
- * Simple activity that attaches a new base context.
- */
-public class AttachedContextActivity extends AbstractAutoFillActivity {
-    static final String ID_INPUT = "input";
-
-    EditText mInput;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.simple_save_activity);
-
-        mInput = findViewById(R.id.input);
-    }
-
-    @Override
-    protected void attachBaseContext(Context newBase) {
-        final Context localContext = applyLocale(newBase, "en");
-        super.attachBaseContext(localContext);
-    }
-
-    private Context applyLocale(Context context, String language) {
-        final Resources resources = context.getResources();
-        final Configuration configuration = resources.getConfiguration();
-        configuration.setLocale(new Locale(language));
-        return context.createConfigurationContext(configuration);
-    }
-
-    FillExpectation expectAutoFill(String input) {
-        final FillExpectation expectation = new FillExpectation(input);
-        mInput.addTextChangedListener(expectation.mInputWatcher);
-        return expectation;
-    }
-
-    final class FillExpectation {
-        private final OneTimeTextWatcher mInputWatcher;
-
-        private FillExpectation(String input) {
-            mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
-        }
-
-        void assertAutoFilled() throws Exception {
-            mInputWatcher.assertAutoFilled();
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
index 97ed220..9d2f2b3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
@@ -16,7 +16,11 @@
 
 package android.autofillservice.cts;
 
-import android.autofillservice.cts.AttachedContextActivity.FillExpectation;
+import android.autofillservice.cts.activities.AttachedContextActivity;
+import android.autofillservice.cts.activities.AttachedContextActivity.FillExpectation;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
 
 import org.junit.Test;
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
deleted file mode 100644
index 7ddfccc..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.ResponseType.NULL;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Parcelable;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.autofill.AutofillManager;
-import android.widget.Button;
-import android.widget.EditText;
-
-import com.google.common.base.Preconditions;
-
-import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * This class simulates authentication at the dataset at reponse level
- */
-public class AuthenticationActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "AuthenticationActivity";
-    private static final String EXTRA_DATASET_ID = "dataset_id";
-    private static final String EXTRA_RESPONSE_ID = "response_id";
-
-    /**
-     * When launched with this intent, it will pass it back to the
-     * {@link AutofillManager#EXTRA_CLIENT_STATE} of the result.
-     */
-    private static final String EXTRA_OUTPUT_CLIENT_STATE = "output_client_state";
-
-
-    private static final int MSG_WAIT_FOR_LATCH = 1;
-    private static final int MSG_REQUEST_AUTOFILL = 2;
-
-    private static Bundle sData;
-    private static final SparseArray<CannedDataset> sDatasets = new SparseArray<>();
-    private static final SparseArray<CannedFillResponse> sResponses = new SparseArray<>();
-    private static final ArrayList<PendingIntent> sPendingIntents = new ArrayList<>();
-
-    private static Object sLock = new Object();
-
-    // Guarded by sLock
-    private static int sResultCode;
-
-    // Guarded by sLock
-    // Used to block response until it's counted down.
-    private static CountDownLatch sResponseLatch;
-
-    // Guarded by sLock
-    // Used to request autofill for a autofillable view in AuthenticationActivity
-    private static boolean sRequestAutofill;
-
-    private Handler mHandler;
-
-    private EditText mPasswordEditText;
-    private Button mYesButton;
-
-    static void resetStaticState() {
-        setResultCode(null, RESULT_OK);
-        setRequestAutofillForAuthenticationActivity(/* requestAutofill */ false);
-        sDatasets.clear();
-        sResponses.clear();
-        for (int i = 0; i < sPendingIntents.size(); i++) {
-            final PendingIntent pendingIntent = sPendingIntents.get(i);
-            Log.d(TAG, "Cancelling " + pendingIntent);
-            pendingIntent.cancel();
-        }
-    }
-
-    /**
-     * Creates an {@link IntentSender} with the given unique id for the given dataset.
-     */
-    public static IntentSender createSender(Context context, int id, CannedDataset dataset) {
-        return createSender(context, id, dataset, null);
-    }
-
-    public static IntentSender createSender(Context context, int id,
-            CannedDataset dataset, Bundle outClientState) {
-        Preconditions.checkArgument(id > 0, "id must be positive");
-        Preconditions.checkState(sDatasets.get(id) == null, "already have id");
-        sDatasets.put(id, dataset);
-        return createSender(context, EXTRA_DATASET_ID, id, outClientState);
-    }
-
-    /**
-     * Creates an {@link IntentSender} with the given unique id for the given fill response.
-     */
-    public static IntentSender createSender(Context context, int id,
-            CannedFillResponse response) {
-        return createSender(context, id, response, null);
-    }
-
-    public static IntentSender createSender(Context context, int id,
-            CannedFillResponse response, Bundle outData) {
-        Preconditions.checkArgument(id > 0, "id must be positive");
-        Preconditions.checkState(sResponses.get(id) == null, "already have id");
-        sResponses.put(id, response);
-        return createSender(context, EXTRA_RESPONSE_ID, id, outData);
-    }
-
-    private static IntentSender createSender(Context context, String extraName, int id,
-            Bundle outClientState) {
-        final Intent intent = new Intent(context, AuthenticationActivity.class);
-        intent.putExtra(extraName, id);
-        if (outClientState != null) {
-            Log.d(TAG, "Create with " + outClientState + " as " + EXTRA_OUTPUT_CLIENT_STATE);
-            intent.putExtra(EXTRA_OUTPUT_CLIENT_STATE, outClientState);
-        }
-        final PendingIntent pendingIntent = PendingIntent.getActivity(context, id, intent, 0);
-        sPendingIntents.add(pendingIntent);
-        return pendingIntent.getIntentSender();
-    }
-
-    /**
-     * Creates an {@link IntentSender} with the given unique id.
-     */
-    public static IntentSender createSender(Context context, int id) {
-        Preconditions.checkArgument(id > 0, "id must be positive");
-        return PendingIntent
-                .getActivity(context, id, new Intent(context, AuthenticationActivity.class),
-                        PendingIntent.FLAG_CANCEL_CURRENT)
-                .getIntentSender();
-    }
-
-    public static Bundle getData() {
-        final Bundle data = sData;
-        sData = null;
-        return data;
-    }
-
-    /**
-     * Sets the value that's passed to {@link Activity#setResult(int, Intent)} when on
-     * {@link Activity#onCreate(Bundle)}.
-     */
-    public static void setResultCode(int resultCode) {
-        synchronized (sLock) {
-            sResultCode = resultCode;
-        }
-    }
-
-    /**
-     * Sets the value that's passed to {@link Activity#setResult(int, Intent)}, but only calls it
-     * after the {@code latch}'s countdown reaches {@code 0}.
-     */
-    public static void setResultCode(CountDownLatch latch, int resultCode) {
-        synchronized (sLock) {
-            sResponseLatch = latch;
-            sResultCode = resultCode;
-        }
-    }
-
-    public static void setRequestAutofillForAuthenticationActivity(boolean requestAutofill) {
-        synchronized (sLock) {
-            sRequestAutofill = requestAutofill;
-        }
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.authentication_activity);
-
-        mPasswordEditText = findViewById(R.id.password);
-        mYesButton = findViewById(R.id.yes);
-        mYesButton.setOnClickListener(view -> doIt());
-
-        mHandler = new Handler(Looper.getMainLooper(), (m) -> {
-            switch (m.what) {
-                case MSG_WAIT_FOR_LATCH:
-                    waitForLatchAndDoIt();
-                    break;
-                case MSG_REQUEST_AUTOFILL:
-                    requestFocusOnPassword();
-                    break;
-                default:
-                    throw new IllegalArgumentException("invalid message: " + m);
-            }
-            return true;
-        });
-
-        if (sResponseLatch != null) {
-            Log.d(TAG, "Delaying message until latch is counted down");
-            mHandler.dispatchMessage(mHandler.obtainMessage(MSG_WAIT_FOR_LATCH));
-        } else if (sRequestAutofill) {
-            mHandler.dispatchMessage(mHandler.obtainMessage(MSG_REQUEST_AUTOFILL));
-        } else {
-            doIt();
-        }
-    }
-
-    private void requestFocusOnPassword() {
-        syncRunOnUiThread(() -> mPasswordEditText.requestFocus());
-    }
-
-    private void waitForLatchAndDoIt() {
-        try {
-            final boolean called = sResponseLatch.await(5, TimeUnit.SECONDS);
-            if (!called) {
-                throw new IllegalStateException("latch not called in 5 seconds");
-            }
-            doIt();
-        } catch (InterruptedException e) {
-            Thread.interrupted();
-            throw new IllegalStateException("interrupted");
-        }
-    }
-
-    private void doIt() {
-        // We should get the assist structure...
-        final AssistStructure structure = getIntent().getParcelableExtra(
-                AutofillManager.EXTRA_ASSIST_STRUCTURE);
-        assertWithMessage("structure not called").that(structure).isNotNull();
-
-        // and the bundle
-        sData = getIntent().getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE);
-        final CannedFillResponse response =
-                sResponses.get(getIntent().getIntExtra(EXTRA_RESPONSE_ID, 0));
-        final CannedDataset dataset =
-                sDatasets.get(getIntent().getIntExtra(EXTRA_DATASET_ID, 0));
-
-        final Parcelable result;
-
-        if (response != null) {
-            if (response.getResponseType() == NULL) {
-                result = null;
-            } else {
-                result = response.asFillResponse(/* contexts= */ null,
-                        (id) -> Helper.findNodeByResourceId(structure, id));
-            }
-        } else if (dataset != null) {
-            result = dataset.asDataset((id) -> Helper.findNodeByResourceId(structure, id));
-        } else {
-            throw new IllegalStateException("no dataset or response");
-        }
-
-        // Pass on the auth result
-        final Intent intent = new Intent();
-        intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, result);
-
-        final Bundle outClientState = getIntent().getBundleExtra(EXTRA_OUTPUT_CLIENT_STATE);
-        if (outClientState != null) {
-            Log.d(TAG, "Adding " + outClientState + " as " + AutofillManager.EXTRA_CLIENT_STATE);
-            intent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, outClientState);
-        }
-
-        final int resultCode;
-        synchronized (sLock) {
-            resultCode = sResultCode;
-        }
-        Log.d(TAG, "Returning code " + resultCode);
-        setResult(resultCode, intent);
-
-        // Done
-        finish();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java
deleted file mode 100644
index 200e184..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java
+++ /dev/null
@@ -1,1192 +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.autofillservice.cts;
-
-import static android.app.Activity.RESULT_CANCELED;
-import static android.app.Activity.RESULT_OK;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.UNUSED_AUTOFILL_VALUE;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillValue;
-
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.regex.Pattern;
-
-public class AuthenticationTest extends AbstractLoginActivityTestCase {
-
-    @Test
-    public void testDatasetAuthTwoFields() throws Exception {
-        datasetAuthTwoFields(false);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthTwoFieldsUserCancelsFirstAttempt() throws Exception {
-        datasetAuthTwoFields(true);
-    }
-
-    private void datasetAuthTwoFields(boolean cancelFirstAttempt) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // Make sure UI is show on 2nd field as well
-        final View password = mActivity.getPassword();
-        requestFocusOnPassword();
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // Now tap on 1st field to show it again...
-        requestFocusOnUsername();
-        callback.assertUiHiddenEvent(password);
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        if (cancelFirstAttempt) {
-            // Trigger the auth dialog, but emulate cancel.
-            AuthenticationActivity.setResultCode(RESULT_CANCELED);
-            mUiBot.selectDataset("Tap to auth dataset");
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(username);
-            mUiBot.assertDatasets("Tap to auth dataset");
-
-            // Make sure it's still shown on other fields...
-            requestFocusOnPassword();
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(password);
-            mUiBot.assertDatasets("Tap to auth dataset");
-
-            // Tap on 1st field to show it again...
-            requestFocusOnUsername();
-            callback.assertUiHiddenEvent(password);
-            callback.assertUiShownEvent(username);
-        }
-
-        // ...and select it this time
-        AuthenticationActivity.setResultCode(RESULT_OK);
-        mUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthTwoFieldsReplaceResponse() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .build());
-
-        // Set up the authentication response client state
-        final Bundle authentionClientState = new Bundle();
-        authentionClientState.putCharSequence("clientStateKey1", "clientStateValue1");
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, (AutofillValue) null)
-                        .setField(ID_PASSWORD, (AutofillValue) null)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .setExtras(authentionClientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        mUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-
-        // Select a dataset from the new response
-        callback.assertUiShownEvent(username);
-        mUiBot.selectDataset("Dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("clientStateKey1");
-        assertThat(extraValue).isEqualTo("clientStateValue1");
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthTwoFieldsNoValues() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intent
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, (String) null)
-                        .setField(ID_PASSWORD, (String) null)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        mUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthTwoDatasets() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .build();
-        final IntentSender authentication1 = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-        final IntentSender authentication2 = AuthenticationActivity.createSender(mContext, 2,
-                unlockedDataset);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset 1"))
-                        .setAuthentication(authentication1)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset 2"))
-                        .setAuthentication(authentication2)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset 1", "Tap to auth dataset 2");
-
-        mUiBot.selectDataset("Tap to auth dataset 1");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthMixedSelectAuth() throws Exception {
-        datasetAuthMixedTest(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthMixedSelectNonAuth() throws Exception {
-        datasetAuthMixedTest(false);
-    }
-
-    private void datasetAuthMixedTest(boolean selectAuth) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE")
-                        .setField(ID_PASSWORD, "SWEET")
-                        .setPresentation(createPresentation("What, me auth?"))
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        if (selectAuth) {
-            mActivity.expectAutoFill("dude", "sweet");
-        } else {
-            mActivity.expectAutoFill("DUDE", "SWEET");
-        }
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
-
-        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
-        mUiBot.selectDataset(chosenOne);
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
-    public void testDatasetAuthNoFiltering() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .build();
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // ..then type something to hide it.
-        mActivity.onUsername((v) -> v.setText("a"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Now delete the char and assert it's shown again...
-        mActivity.onUsername((v) -> v.setText(""));
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // ...and select it this time
-        mUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
-    public void testDatasetAuthFilteringUsingAutofillValue() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .build();
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("DS1"))
-                        .setAuthentication(authentication)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE,THE")
-                        .setField(ID_PASSWORD, "SWEET")
-                        .setPresentation(createPresentation("DS2"))
-                        .setAuthentication(authentication)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ZzBottom")
-                        .setField(ID_PASSWORD, "top")
-                        .setPresentation(createPresentation("DS3"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("DS1", "DS2", "DS3");
-
-        // ...then type something to hide them.
-        mActivity.onUsername((v) -> v.setText("a"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Now delete the char and assert they're shown again...
-        mActivity.onUsername((v) -> v.setText(""));
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("DS1", "DS2", "DS3");
-
-        // ...then filter for 2
-        mActivity.onUsername((v) -> v.setText("d"));
-        mUiBot.assertDatasets("DS1", "DS2");
-
-        // ...up to 1
-        mActivity.onUsername((v) -> v.setText("du"));
-        mUiBot.assertDatasets("DS1", "DS2");
-        mActivity.onUsername((v) -> v.setText("dud"));
-        mUiBot.assertDatasets("DS1", "DS2");
-        mActivity.onUsername((v) -> v.setText("dude"));
-        mUiBot.assertDatasets("DS1", "DS2");
-        mActivity.onUsername((v) -> v.setText("dude,"));
-        mUiBot.assertDatasets("DS2");
-
-        // Now delete the char and assert 2 are shown again...
-        mActivity.onUsername((v) -> v.setText("dude"));
-        final UiObject2 picker = mUiBot.assertDatasets("DS1", "DS2");
-
-        // ...and select it this time
-        mUiBot.selectDataset(picker, "DS1");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testDatasetAuthFilteringUsingRegex() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .build();
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-
-        // Configure the service behavior
-
-        final Pattern min2Chars = Pattern.compile(".{2,}");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE, min2Chars)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // ...then type something to hide it.
-        mActivity.onUsername((v) -> v.setText("a"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // ...now type something again to show it, as the input will have 2 chars.
-        mActivity.onUsername((v) -> v.setText("aa"));
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // Delete the char and assert it's not shown again...
-        mActivity.onUsername((v) -> v.setText("a"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // ...then type something again to show it, as the input will have 2 chars.
-        mActivity.onUsername((v) -> v.setText("aa"));
-        callback.assertUiShownEvent(username);
-
-        // ...and select it this time
-        mUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
-    public void testDatasetAuthMixedFilteringSelectAuth() throws Exception {
-        datasetAuthMixedFilteringTest(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
-    public void testDatasetAuthMixedFilteringSelectNonAuth() throws Exception {
-        datasetAuthMixedFilteringTest(false);
-    }
-
-    private void datasetAuthMixedFilteringTest(boolean selectAuth) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "DUDE")
-                .setField(ID_PASSWORD, "SWEET")
-                .build();
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("What, me auth?"))
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        if (selectAuth) {
-            mActivity.expectAutoFill("DUDE", "SWEET");
-        } else {
-            mActivity.expectAutoFill("dude", "sweet");
-        }
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
-
-        // Filter the auth dataset.
-        mActivity.onUsername((v) -> v.setText("d"));
-        mUiBot.assertDatasets("What, me auth?");
-
-        // Filter all.
-        mActivity.onUsername((v) -> v.setText("dw"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Now delete the char and assert the non-auth is shown again.
-        mActivity.onUsername((v) -> v.setText("d"));
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("What, me auth?");
-
-        // Delete again and assert all dataset are shown.
-        mActivity.onUsername((v) -> v.setText(""));
-        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
-
-        // ...and select it this time
-        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
-        mUiBot.selectDataset(chosenOne);
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testDatasetAuthClientStateSetOnIntentOnly() throws Exception {
-        fillDatasetAuthWithClientState(ClientStateLocation.INTENT_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthClientStateSetOnIntentOnly() is enough")
-    public void testDatasetAuthClientStateSetOnFillResponseOnly() throws Exception {
-        fillDatasetAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthClientStateSetOnIntentOnly() is enough")
-    public void testDatasetAuthClientStateSetOnIntentAndFillResponse() throws Exception {
-        fillDatasetAuthWithClientState(ClientStateLocation.BOTH);
-    }
-
-    private void fillDatasetAuthWithClientState(ClientStateLocation where) throws Exception {
-        // Set service.
-        enableService();
-
-        // Prepare the authenticated response
-        final CannedDataset dataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .build();
-        final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
-                ? AuthenticationActivity.createSender(mContext, 1,
-                        dataset)
-                : AuthenticationActivity.createSender(mContext, 1,
-                        dataset, Helper.newClientState("CSI", "FromIntent"));
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setExtras(Helper.newClientState("CSI", "FromResponse"))
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Tap authentication request.
-        mUiBot.selectDataset("Tap to auth dataset");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Now trigger save.
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert client state on authentication activity.
-        Helper.assertAuthenticationClientState("auth activity", AuthenticationActivity.getData(),
-                "CSI", "FromResponse");
-
-        // Assert client state on save request.
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
-                ? "FromResponse" : "FromIntent";
-        Helper.assertAuthenticationClientState("on save", saveRequest.data, "CSI", expectedValue);
-    }
-
-    @Test
-    public void testFillResponseAuthBothFields() throws Exception {
-        fillResponseAuthBothFields(false);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
-    public void testFillResponseAuthBothFieldsUserCancelsFirstAttempt() throws Exception {
-        fillResponseAuthBothFields(true);
-    }
-
-    private void fillResponseAuthBothFields(boolean cancelFirstAttempt) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final Bundle clientState = new Bundle();
-        clientState.putString("numbers", "4815162342");
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setId("name")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .setExtras(clientState).build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .setExtras(clientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // Make sure UI is show on 2nd field as well
-        final View password = mActivity.getPassword();
-        requestFocusOnPassword();
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // Now tap on 1st field to show it again...
-        requestFocusOnUsername();
-        callback.assertUiHiddenEvent(password);
-        callback.assertUiShownEvent(username);
-
-        if (cancelFirstAttempt) {
-            // Trigger the auth dialog, but emulate cancel.
-            AuthenticationActivity.setResultCode(RESULT_CANCELED);
-            mUiBot.selectDataset("Tap to auth response");
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(username);
-            mUiBot.assertDatasets("Tap to auth response");
-
-            // Make sure it's still shown on other fields...
-            requestFocusOnPassword();
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(password);
-            mUiBot.assertDatasets("Tap to auth response");
-
-            // Tap on 1st field to show it again...
-            requestFocusOnUsername();
-            callback.assertUiHiddenEvent(password);
-            callback.assertUiShownEvent(username);
-        }
-
-        // ...and select it this time
-        AuthenticationActivity.setResultCode(RESULT_OK);
-        mUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(username);
-        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
-        mUiBot.selectDataset(picker, "Dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("numbers");
-        assertThat(extraValue).isEqualTo("4815162342");
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
-    public void testFillResponseAuthJustOneField() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final Bundle clientState = new Bundle();
-        clientState.putString("numbers", "4815162342");
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME)
-                .setIgnoreFields(ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .setExtras(clientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // Make sure UI is not show on 2nd field
-        requestFocusOnPassword();
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-        // Now tap on 1st field to show it again...
-        requestFocusOnUsername();
-        callback.assertUiShownEvent(username);
-
-        // ...and select it this time
-        mUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
-
-        callback.assertUiShownEvent(username);
-        mUiBot.selectDataset(picker, "Dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("numbers");
-        assertThat(extraValue).isEqualTo("4815162342");
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
-    public void testFillResponseAuthWhenAppCallsCancel() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setId("name")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .build());
-
-        // Trigger autofill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // Disables autofill so it's not triggered again after the auth activity is finished
-        // (and current session is canceled) and the login activity is resumed.
-        username.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO);
-
-        // Autofill it.
-        final CountDownLatch latch = new CountDownLatch(1);
-        AuthenticationActivity.setResultCode(latch, RESULT_OK);
-
-        mUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-
-        // Cancel session...
-        mActivity.getAutofillManager().cancel();
-
-        // ...before finishing the Auth UI.
-        latch.countDown();
-
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
-    public void testFillResponseAuthServiceHasNoDataButCanSave() throws Exception {
-        fillResponseAuthServiceHasNoDataTest(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
-    public void testFillResponseAuthServiceHasNoData() throws Exception {
-        fillResponseAuthServiceHasNoDataTest(false);
-    }
-
-    private void fillResponseAuthServiceHasNoDataTest(boolean canSave) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final CannedFillResponse response = canSave
-                ? new CannedFillResponse.Builder()
-                        .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                        .build()
-                : CannedFillResponse.NO_RESPONSE;
-
-        final IntentSender authentication =
-                AuthenticationActivity.createSender(mContext, 1, response);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .build());
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-
-        // Select the authentication dialog.
-        mUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        if (!canSave) {
-            // Our work is done!
-            return;
-        }
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNoUnhandledSaveRequests();
-        assertThat(saveRequest.datasetIds).isNull();
-
-        // Assert value of expected fields - should not be sanitized.
-        final ViewNode usernameNode = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(usernameNode, "malkovich");
-        final ViewNode passwordNode = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-        assertTextAndValue(passwordNode, "malkovich");
-    }
-
-    @Test
-    public void testFillResponseAuthClientStateSetOnIntentOnly() throws Exception {
-        fillResponseAuthWithClientState(ClientStateLocation.INTENT_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthClientStateSetOnIntentOnly() is enough")
-    public void testFillResponseAuthClientStateSetOnFillResponseOnly() throws Exception {
-        fillResponseAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthClientStateSetOnIntentOnly() is enough")
-    public void testFillResponseAuthClientStateSetOnIntentAndFillResponse() throws Exception {
-        fillResponseAuthWithClientState(ClientStateLocation.BOTH);
-    }
-
-    enum ClientStateLocation {
-        INTENT_ONLY,
-        FILL_RESPONSE_ONLY,
-        BOTH
-    }
-
-    private void fillResponseAuthWithClientState(ClientStateLocation where) throws Exception {
-        // Set service.
-        enableService();
-
-        // Prepare the authenticated response
-        final CannedFillResponse.Builder authenticatedResponseBuilder =
-                new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("Dataset"))
-                        .build());
-
-        if (where == ClientStateLocation.FILL_RESPONSE_ONLY || where == ClientStateLocation.BOTH) {
-            authenticatedResponseBuilder.setExtras(
-                    Helper.newClientState("CSI", "FromAuthResponse"));
-        }
-
-        final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
-                ? AuthenticationActivity.createSender(mContext, 1,
-                authenticatedResponseBuilder.build())
-                : AuthenticationActivity.createSender(mContext, 1,
-                        authenticatedResponseBuilder.build(),
-                        Helper.newClientState("CSI", "FromIntent"));
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME)
-                .setIgnoreFields(ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .setExtras(Helper.newClientState("CSI", "FromResponse"))
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Tap authentication request.
-        mUiBot.selectDataset("Tap to auth response");
-
-        // Tap dataset.
-        mUiBot.selectDataset("Dataset");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Now trigger save.
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert client state on authentication activity.
-        Helper.assertAuthenticationClientState("auth activity", AuthenticationActivity.getData(),
-                "CSI", "FromResponse");
-
-        // Assert client state on save request.
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
-                ? "FromAuthResponse" : "FromIntent";
-        Helper.assertAuthenticationClientState("on save", saveRequest.data, "CSI", expectedValue);
-    }
-
-    @Test
-    public void testFillResponseFiltering() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final Bundle clientState = new Bundle();
-        clientState.putString("numbers", "4815162342");
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setId("name")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .setExtras(clientState).build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .setExtras(clientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // ..then type something to hide it.
-        mActivity.onUsername((v) -> v.setText("a"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Now delete the char and assert it's shown again...
-        mActivity.onUsername((v) -> v.setText(""));
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // ...and select it this time
-        AuthenticationActivity.setResultCode(RESULT_OK);
-        mUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(username);
-        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
-        mUiBot.selectDataset(picker, "Dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("numbers");
-        assertThat(extraValue).isEqualTo("4815162342");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
deleted file mode 100644
index f0e1179..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ /dev/null
@@ -1,468 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_NAME;
-import static android.content.Context.CLIPBOARD_SERVICE;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import android.app.PendingIntent;
-import android.autofillservice.cts.InstrumentedAutoFillService.Replier;
-import android.autofillservice.cts.augmented.AugmentedAuthActivity;
-import android.autofillservice.cts.inline.InlineUiBot;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.service.autofill.InlinePresentation;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-import android.widget.RemoteViews;
-
-import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
-import com.android.compatibility.common.util.RequiredFeatureRule;
-import com.android.compatibility.common.util.RetryRule;
-import com.android.compatibility.common.util.SafeCleanerRule;
-import com.android.compatibility.common.util.SettingsStateKeeperRule;
-import com.android.compatibility.common.util.TestNameUtils;
-import com.android.cts.mockime.ImeSettings;
-import com.android.cts.mockime.MockImeSessionRule;
-
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-
-/**
- * Placeholder for the base class for all integration tests:
- *
- * <ul>
- *   <li>{@link AutoActivityLaunch}
- *   <li>{@link ManualActivityLaunch}
- * </ul>
- *
- * <p>These classes provide the common infrastructure such as:
- *
- * <ul>
- *   <li>Preserving the autofill service settings.
- *   <li>Cleaning up test state.
- *   <li>Wrapping the test under autofill-specific test rules.
- *   <li>Launching the activity used by the test.
- * </ul>
- */
-public final class AutoFillServiceTestCase {
-
-    /**
-     * Base class for all test cases that use an {@link AutofillActivityTestRule} to
-     * launch the activity.
-     */
-    // Must be public because of @ClassRule
-    public abstract static class AutoActivityLaunch<A extends AbstractAutoFillActivity>
-            extends BaseTestCase {
-
-        /**
-         * Returns if inline suggestion is enabled.
-         */
-        protected boolean isInlineMode() {
-            return false;
-        }
-
-        protected static UiBot getInlineUiBot() {
-            return sDefaultUiBot2;
-        }
-
-        protected static UiBot getDropdownUiBot() {
-            return sDefaultUiBot;
-        }
-
-        @ClassRule
-        public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
-                sTheRealServiceSettingsKeeper;
-
-        protected AutoActivityLaunch() {
-            super(sDefaultUiBot);
-        }
-        protected AutoActivityLaunch(UiBot uiBot) {
-            super(uiBot);
-        }
-
-        @Override
-        protected TestRule getMainTestRule() {
-            return getActivityRule();
-        }
-
-        /**
-         * Gets the rule to launch the main activity for this test.
-         *
-         * <p><b>Note: </b>the rule must be either lazily generated or a static singleton, otherwise
-         * this method could return {@code null} when the rule chain that uses it is constructed.
-         *
-         */
-        protected abstract @NonNull AutofillActivityTestRule<A> getActivityRule();
-
-        protected @NonNull A launchActivity(@NonNull Intent intent) {
-            return getActivityRule().launchActivity(intent);
-        }
-
-        protected @NonNull A getActivity() {
-            return getActivityRule().getActivity();
-        }
-    }
-
-    /**
-     * Base class for all test cases that don't require an {@link AutofillActivityTestRule}.
-     */
-    // Must be public because of @ClassRule
-    public abstract static class ManualActivityLaunch extends BaseTestCase {
-
-        @ClassRule
-        public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
-                sTheRealServiceSettingsKeeper;
-
-        protected ManualActivityLaunch() {
-            this(sDefaultUiBot);
-        }
-
-        protected ManualActivityLaunch(@NonNull UiBot uiBot) {
-            super(uiBot);
-        }
-
-        @Override
-        protected TestRule getMainTestRule() {
-            // TODO: create a NoOpTestRule on common code
-            return new TestRule() {
-
-                @Override
-                public Statement apply(Statement base, Description description) {
-                    // Returns a no-op statements
-                    return new Statement() {
-                        @Override
-                        public void evaluate() throws Throwable {
-                            base.evaluate();
-                        }
-                    };
-                }
-            };
-        }
-
-        protected SimpleSaveActivity startSimpleSaveActivity() throws Exception {
-            final Intent intent = new Intent(mContext, SimpleSaveActivity.class)
-                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            mContext.startActivity(intent);
-            mUiBot.assertShownByRelativeId(SimpleSaveActivity.ID_LABEL);
-            return SimpleSaveActivity.getInstance();
-        }
-
-        protected PreSimpleSaveActivity startPreSimpleSaveActivity() throws Exception {
-            final Intent intent = new Intent(mContext, PreSimpleSaveActivity.class)
-                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            mContext.startActivity(intent);
-            mUiBot.assertShownByRelativeId(PreSimpleSaveActivity.ID_PRE_LABEL);
-            return PreSimpleSaveActivity.getInstance();
-        }
-    }
-
-    @RunWith(AndroidJUnit4.class)
-    // Must be public because of @ClassRule
-    public abstract static class BaseTestCase {
-
-        private static final String TAG = "AutoFillServiceTestCase";
-
-        protected static final Replier sReplier = InstrumentedAutoFillService.getReplier();
-
-        protected static final Context sContext = getInstrumentation().getTargetContext();
-
-        // Hack because JUnit requires that @ClassRule instance belong to a public class.
-        protected static final SettingsStateKeeperRule sTheRealServiceSettingsKeeper =
-                new SettingsStateKeeperRule(sContext, Settings.Secure.AUTOFILL_SERVICE) {
-            @Override
-            protected void preEvaluate(Description description) {
-                TestNameUtils.setCurrentTestClass(description.getClassName());
-            }
-
-            @Override
-            protected void postEvaluate(Description description) {
-                TestNameUtils.setCurrentTestClass(null);
-            }
-        };
-
-        public static final MockImeSessionRule sMockImeSessionRule = new MockImeSessionRule(
-                InstrumentationRegistry.getTargetContext(),
-                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                new ImeSettings.Builder().setInlineSuggestionsEnabled(true)
-                        .setInlineSuggestionViewContentDesc(InlineUiBot.SUGGESTION_STRIP_DESC));
-
-        protected static final RequiredFeatureRule sRequiredFeatureRule =
-                new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
-
-        private final AutofillTestWatcher mTestWatcher = new AutofillTestWatcher();
-
-        private final RetryRule mRetryRule =
-                new RetryRule(getNumberRetries(), () -> {
-                    // Between testing and retries, clean all launched activities to avoid
-                    // exception:
-                    //     Could not launch intent Intent { ... } within 45 seconds.
-                    mTestWatcher.cleanAllActivities();
-                });
-
-        private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
-
-        protected final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
-                .setDumper(mLoggingRule)
-                .run(() -> sReplier.assertNoUnhandledFillRequests())
-                .run(() -> sReplier.assertNoUnhandledSaveRequests())
-                .add(() -> { return sReplier.getExceptions(); });
-
-        @Rule
-        public final RuleChain mLookAllTheseRules = RuleChain
-                //
-                // requiredFeatureRule should be first so the test can be skipped right away
-                .outerRule(getRequiredFeaturesRule())
-                //
-                // mTestWatcher should always be one the first rules, as it defines the name of the
-                // test being ran and finishes dangling activities at the end
-                .around(mTestWatcher)
-                //
-                // sMockImeSessionRule make sure MockImeSession.create() is used to launch mock IME
-                .around(sMockImeSessionRule)
-                //
-                // mLoggingRule wraps the test but doesn't interfere with it
-                .around(mLoggingRule)
-                //
-                // mSafeCleanerRule will catch errors
-                .around(mSafeCleanerRule)
-                //
-                // mRetryRule should be closest to the main test as possible
-                .around(mRetryRule)
-                //
-                // Augmented Autofill should be disabled by default
-                .around(new DeviceConfigStateChangerRule(sContext, DeviceConfig.NAMESPACE_AUTOFILL,
-                        AutofillManager.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES,
-                        Integer.toString(getSmartSuggestionMode())))
-                //
-                // Finally, let subclasses add their own rules (like ActivityTestRule)
-                .around(getMainTestRule());
-
-
-        protected final Context mContext = sContext;
-        protected final String mPackageName;
-        protected final UiBot mUiBot;
-
-        private BaseTestCase(@NonNull UiBot uiBot) {
-            mPackageName = mContext.getPackageName();
-            mUiBot = uiBot;
-            mUiBot.reset();
-        }
-
-        protected int getSmartSuggestionMode() {
-            return AutofillManager.FLAG_SMART_SUGGESTION_OFF;
-        }
-
-        /**
-         * Gets how many times a test should be retried.
-         *
-         * @return {@code 1} by default, unless overridden by subclasses or by a global settings
-         * named {@code CLASS_NAME + #getNumberRetries} or
-         * {@code CtsAutoFillServiceTestCases#getNumberRetries} (the former having a higher
-         * priority).
-         */
-        protected int getNumberRetries() {
-            final String localProp = getClass().getName() + "#getNumberRetries";
-            final Integer localValue = getNumberRetries(localProp);
-            if (localValue != null) return localValue.intValue();
-
-            final String globalProp = "CtsAutoFillServiceTestCases#getNumberRetries";
-            final Integer globalValue = getNumberRetries(globalProp);
-            if (globalValue != null) return globalValue.intValue();
-
-            return 1;
-        }
-
-        private Integer getNumberRetries(String prop) {
-            final String value = Settings.Global.getString(sContext.getContentResolver(), prop);
-            if (value != null) {
-                Log.i(TAG, "getNumberRetries(): overriding to " + value + " because of '" + prop
-                        + "' global setting");
-                try {
-                    return Integer.parseInt(value);
-                } catch (Exception e) {
-                    Log.w(TAG, "error parsing property '" + prop + "'='" + value + "'", e);
-                }
-            }
-            return null;
-        }
-
-        /**
-         * Gets a rule that defines which features must be present for this test to run.
-         *
-         * <p>By default it returns a rule that requires {@link PackageManager#FEATURE_AUTOFILL},
-         * but subclass can override to be more specific.
-         */
-        @NonNull
-        protected TestRule getRequiredFeaturesRule() {
-            return sRequiredFeatureRule;
-        }
-
-        /**
-         * Gets the test-specific {@link Rule @Rule}.
-         *
-         * <p>Sub-class <b>MUST</b> override this method instead of annotation their own rules,
-         * so the order is preserved.
-         *
-         */
-        @NonNull
-        protected abstract TestRule getMainTestRule();
-
-        @BeforeClass
-        public static void disableDefaultAugmentedService() {
-            Log.v(TAG, "@BeforeClass: disableDefaultAugmentedService()");
-            Helper.setDefaultAugmentedAutofillServiceEnabled(false);
-        }
-
-        @AfterClass
-        public static void enableDefaultAugmentedService() {
-            Log.v(TAG, "@AfterClass: enableDefaultAugmentedService()");
-            Helper.setDefaultAugmentedAutofillServiceEnabled(true);
-        }
-
-        @Before
-        public void prepareDevice() throws Exception {
-            Log.v(TAG, "@Before: prepareDevice()");
-
-            // Unlock screen.
-            runShellCommand("input keyevent KEYCODE_WAKEUP");
-
-            // Dismiss keyguard, in case it's set as "Swipe to unlock".
-            runShellCommand("wm dismiss-keyguard");
-
-            // Collapse notifications.
-            runShellCommand("cmd statusbar collapse");
-
-            // Set orientation as portrait, otherwise some tests might fail due to elements not
-            // fitting in, IME orientation, etc...
-            mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-
-            // Wait until device is idle to avoid flakiness
-            mUiBot.waitForIdle();
-
-            // Clear Clipboard
-            // TODO(b/117768051): remove try/catch once fixed
-            try {
-                ((ClipboardManager) mContext.getSystemService(CLIPBOARD_SERVICE))
-                    .clearPrimaryClip();
-            } catch (Exception e) {
-                Log.e(TAG, "Ignoring exception clearing clipboard", e);
-            }
-        }
-
-        @Before
-        public void preTestCleanup() {
-            Log.v(TAG, "@Before: preTestCleanup()");
-
-            prepareServicePreTest();
-
-            InstrumentedAutoFillService.resetStaticState();
-            AuthenticationActivity.resetStaticState();
-            AugmentedAuthActivity.resetStaticState();
-            sReplier.reset();
-        }
-
-        /**
-         * Prepares the service before each test - by default, disables it
-         */
-        protected void prepareServicePreTest() {
-            Log.v(TAG, "prepareServicePreTest(): calling disableService()");
-            disableService();
-        }
-
-        /**
-         * Enables the {@link InstrumentedAutoFillService} for autofill for the current user.
-         */
-        protected void enableService() {
-            Helper.enableAutofillService(getContext(), SERVICE_NAME);
-        }
-
-        /**
-         * Disables the {@link InstrumentedAutoFillService} for autofill for the current user.
-         */
-        protected void disableService() {
-            Helper.disableAutofillService(getContext());
-        }
-
-        /**
-         * Asserts that the {@link InstrumentedAutoFillService} is enabled for the default user.
-         */
-        protected void assertServiceEnabled() {
-            Helper.assertAutofillServiceStatus(SERVICE_NAME, true);
-        }
-
-        /**
-         * Asserts that the {@link InstrumentedAutoFillService} is disabled for the default user.
-         */
-        protected void assertServiceDisabled() {
-            Helper.assertAutofillServiceStatus(SERVICE_NAME, false);
-        }
-
-        protected RemoteViews createPresentation(String message) {
-            return Helper.createPresentation(message);
-        }
-
-        protected RemoteViews createPresentationWithCancel(String message) {
-            final RemoteViews presentation = new RemoteViews(getContext()
-                    .getPackageName(), R.layout.list_item_cancel);
-            presentation.setTextViewText(R.id.text1, message);
-            return presentation;
-        }
-
-        protected InlinePresentation createInlinePresentation(String message) {
-            return Helper.createInlinePresentation(message);
-        }
-
-        protected InlinePresentation createInlinePresentation(String message,
-                                                              PendingIntent attribution) {
-            return Helper.createInlinePresentation(message, attribution);
-        }
-
-        @NonNull
-        protected AutofillManager getAutofillManager() {
-            return mContext.getSystemService(AutofillManager.class);
-        }
-    }
-
-    protected static final UiBot sDefaultUiBot = new UiBot();
-    protected static final UiBot sDefaultUiBot2 = new InlineUiBot();
-
-    private AutoFillServiceTestCase() {
-        throw new UnsupportedOperationException("Contain static stuff only");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
index 5f2d476..59a51d3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
@@ -16,15 +16,20 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.FragmentContainerActivity.FRAGMENT_TAG;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
+import static android.autofillservice.cts.activities.FragmentContainerActivity.FRAGMENT_TAG;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.Fragment;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.activities.FragmentContainerActivity;
+import android.autofillservice.cts.activities.ManualAuthenticationActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
 import android.content.Intent;
 import android.service.autofill.SaveInfo;
 import android.view.ViewGroup;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java
deleted file mode 100644
index b542bc7..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import androidx.test.rule.ActivityTestRule;
-
-/**
- * Custom {@link ActivityTestRule}.
- */
-public class AutofillActivityTestRule<T extends AbstractAutoFillActivity>
-        extends ActivityTestRule<T> {
-
-    public AutofillActivityTestRule(Class<T> activityClass) {
-        super(activityClass);
-    }
-
-    public AutofillActivityTestRule(Class<T> activityClass, boolean launchActivity) {
-        super(activityClass, false, launchActivity);
-    }
-
-    @Override
-    protected void afterActivityFinished() {
-        // AutofillTestWatcher does not need to watch for this activity as the ActivityTestRule
-        // will take care of finishing it...
-        AutofillTestWatcher.unregisterActivity("AutofillActivityTestRule.afterActivityFinished()",
-                getActivity());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
deleted file mode 100644
index 07ce173..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.compatibility.common.util.SafeCleanerRule;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that improves autofill-related logging by:
- *
- * <ol>
- *   <li>Setting logging level to verbose before test start.
- *   <li>Call {@code dumpsys autofill} in case of failure.
- * </ol>
- */
-public class AutofillLoggingTestRule implements TestRule, SafeCleanerRule.Dumper {
-
-    private static final String TAG = "AutofillLoggingTestRule";
-
-    private final String mTag;
-    private boolean mDumped;
-
-    public AutofillLoggingTestRule(String tag) {
-        mTag = tag;
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                final String testName = description.getDisplayName();
-                final String levelBefore = runShellCommand("cmd autofill get log_level");
-                if (!levelBefore.equals("verbose")) {
-                    runShellCommand("cmd autofill set log_level verbose");
-                }
-                try {
-                    base.evaluate();
-                } catch (Throwable t) {
-                    dump(testName, t);
-                    throw t;
-                } finally {
-                    try {
-                        if (!levelBefore.equals("verbose")) {
-                            runShellCommand("cmd autofill set log_level %s", levelBefore);
-                        }
-                    } finally {
-                        Log.v(TAG, "@After " + testName);
-                    }
-                }
-            }
-        };
-    }
-
-    @Override
-    public void dump(@NonNull String testName, @NonNull Throwable t) {
-        if (mDumped) {
-            Log.e(mTag, "dump(" + testName + "): already dumped");
-            return;
-        }
-        if ((t instanceof AssumptionViolatedException)) {
-            // This exception is used to indicate a test should be skipped and is
-            // ignored by JUnit runners - we don't need to dump it...
-            Log.w(TAG, "ignoring exception: " + t);
-            return;
-        }
-        Log.e(mTag, "Dumping after exception on " + testName, t);
-        Helper.dumpAutofillService(mTag);
-        final String activityDump = runShellCommand("dumpsys activity top");
-        Log.e(mTag, "top activity dump: \n" + activityDump);
-        mDumped = true;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java
deleted file mode 100644
index 2c5dc0c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import android.content.Context;
-import android.content.Intent;
-import android.view.View;
-
-import org.junit.Test;
-
-/**
- * Tests whether autofill save dialog is shown as expected.
- */
-public class AutofillSaveDialogTest extends AutoFillServiceTestCase.ManualActivityLaunch {
-
-    @Test
-    public void testShowSaveUiWhenLaunchActivityWithFlagClearTopAndSingleTop() throws Exception {
-        // Set service.
-        enableService();
-
-        // Start SimpleBeforeLoginActivity before login activity.
-        startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
-                Intent.FLAG_ACTIVITY_NEW_TASK);
-        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
-
-        // Start LoginActivity.
-        startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
-                /* flags= */ 0);
-        mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
-                .build());
-
-        // Trigger autofill on username.
-        LoginActivity loginActivity = LoginActivity.getCurrentActivity();
-        loginActivity.onUsername(View::requestFocus);
-
-        // Wait for fill request to be processed.
-        sReplier.getNextFillRequest();
-
-        // Set data.
-        loginActivity.onUsername((v) -> v.setText("test"));
-
-        // Start SimpleAfterLoginActivity after login activity.
-        startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class, /* flags= */ 0);
-        mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_USERNAME);
-
-        // Restart SimpleBeforeLoginActivity with CLEAR_TOP and SINGLE_TOP.
-        startActivityWithFlag(SimpleAfterLoginActivity.getCurrentActivity(),
-                SimpleBeforeLoginActivity.class,
-                Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
-        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
-
-        // Verify save ui dialog.
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
-    }
-
-    @Test
-    public void testShowSaveUiWhenLaunchActivityWithFlagClearTaskAndNewTask() throws Exception {
-        // Set service.
-        enableService();
-
-        // Start SimpleBeforeLoginActivity before login activity.
-        startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
-                Intent.FLAG_ACTIVITY_NEW_TASK);
-        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
-
-        // Start LoginActivity.
-        startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
-                /* flags= */ 0);
-        mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
-                .build());
-
-        // Trigger autofill on username.
-        LoginActivity loginActivity = LoginActivity.getCurrentActivity();
-        loginActivity.onUsername(View::requestFocus);
-
-        // Wait for fill request to be processed.
-        sReplier.getNextFillRequest();
-
-        // Set data.
-        loginActivity.onUsername((v) -> v.setText("test"));
-
-        // Start SimpleAfterLoginActivity with CLEAR_TASK and NEW_TASK after login activity.
-        startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class,
-                Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
-        mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
-
-        // Verify save ui dialog.
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
-    }
-
-    private void startActivityWithFlag(Context context, Class<?> clazz, int flags) {
-        final Intent intent = new Intent(context, clazz);
-        intent.setFlags(flags);
-        context.startActivity(intent);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
deleted file mode 100644
index ce2c7a2..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
+++ /dev/null
@@ -1,165 +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.autofillservice.cts;
-
-import android.util.ArraySet;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.TestNameUtils;
-
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-
-import java.util.Set;
-
-/**
- * Custom {@link TestWatcher} that's the outer rule of all {@link AutoFillServiceTestCase} tests.
- *
- * <p>This class is not thread safe, but should be fine...
- */
-public final class AutofillTestWatcher extends TestWatcher {
-
-    /**
-     * Cleans up all launched activities between the tests and retries.
-     */
-    public void cleanAllActivities() {
-        try {
-            finishActivities();
-            waitUntilAllDestroyed();
-        } finally {
-            resetStaticState();
-        }
-    }
-
-    private static final String TAG = "AutofillTestWatcher";
-
-    @GuardedBy("sUnfinishedBusiness")
-    private static final Set<AbstractAutoFillActivity> sUnfinishedBusiness = new ArraySet<>();
-
-    @GuardedBy("sAllActivities")
-    private static final Set<AbstractAutoFillActivity> sAllActivities = new ArraySet<>();
-
-    @Override
-    protected void starting(Description description) {
-        resetStaticState();
-        final String testName = description.getDisplayName();
-        Log.i(TAG, "Starting " + testName);
-        TestNameUtils.setCurrentTestName(testName);
-    }
-
-    @Override
-    protected void finished(Description description) {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        final String testName = description.getDisplayName();
-        cleanAllActivities();
-        Log.i(TAG, "Finished " + testName);
-        TestNameUtils.setCurrentTestName(null);
-    }
-
-    private void resetStaticState() {
-        synchronized (sUnfinishedBusiness) {
-            sUnfinishedBusiness.clear();
-        }
-        synchronized (sAllActivities) {
-            sAllActivities.clear();
-        }
-    }
-
-    /**
-     * Registers an activity so it's automatically finished (if necessary) after the test.
-     */
-    public static void registerActivity(@NonNull String where,
-            @NonNull AbstractAutoFillActivity activity) {
-        synchronized (sUnfinishedBusiness) {
-            if (sUnfinishedBusiness.contains(activity)) {
-                throw new IllegalStateException("Already registered " + activity);
-            }
-            Log.v(TAG, "registering activity on " + where + ": " + activity);
-            sUnfinishedBusiness.add(activity);
-            sAllActivities.add(activity);
-        }
-        synchronized (sAllActivities) {
-            sAllActivities.add(activity);
-
-        }
-    }
-
-    /**
-     * Unregisters an activity so it's not automatically finished after the test.
-     */
-    public static void unregisterActivity(@NonNull String where,
-            @NonNull AbstractAutoFillActivity activity) {
-        synchronized (sUnfinishedBusiness) {
-            final boolean unregistered = sUnfinishedBusiness.remove(activity);
-            if (unregistered) {
-                Log.d(TAG, "unregistered activity on " + where + ": " + activity);
-            } else {
-                Log.v(TAG, "ignoring already unregistered activity on " + where + ": " + activity);
-            }
-        }
-    }
-
-    /**
-     * Gets the instance of a previously registered activity.
-     */
-    @Nullable
-    public static <A extends AbstractAutoFillActivity> A getActivity(@NonNull Class<A> clazz) {
-        @SuppressWarnings("unchecked")
-        final A activity = (A) sAllActivities.stream().filter(a -> a.getClass().equals(clazz))
-                .findFirst()
-                .get();
-        return activity;
-    }
-
-    private void finishActivities() {
-        synchronized (sUnfinishedBusiness) {
-            if (sUnfinishedBusiness.isEmpty()) {
-                return;
-            }
-            Log.d(TAG, "Manually finishing " + sUnfinishedBusiness.size() + " activities");
-            for (AbstractAutoFillActivity activity : sUnfinishedBusiness) {
-                if (activity.isFinishing()) {
-                    Log.v(TAG, "Ignoring activity that isFinishing(): " + activity);
-                } else {
-                    Log.d(TAG, "Finishing activity: " + activity);
-                    activity.finishOnly();
-                }
-            }
-        }
-    }
-
-    private void waitUntilAllDestroyed() {
-        synchronized (sAllActivities) {
-            if (sAllActivities.isEmpty()) return;
-
-            Log.d(TAG, "Waiting until " + sAllActivities.size() + " activities are destroyed");
-            for (AbstractAutoFillActivity activity : sAllActivities) {
-                Log.d(TAG, "Waiting for " + activity);
-                try {
-                    activity.waintUntilDestroyed(Timeouts.ACTIVITY_RESURRECTION);
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "interrupted waiting for " + activity + " to be destroyed");
-                    Thread.currentThread().interrupt();
-                }
-            }
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
deleted file mode 100644
index 5446b30..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
+++ /dev/null
@@ -1,543 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.icu.util.Calendar;
-import android.platform.test.annotations.AppModeFull;
-import android.view.View;
-import android.view.autofill.AutofillValue;
-import android.widget.CompoundButton;
-import android.widget.DatePicker;
-import android.widget.EditText;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.Spinner;
-import android.widget.TimePicker;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.junit.Test;
-
-/*
- * TODO: refactor this class.
- *
- * It has 2 types of tests:
- *  1. unit tests that asserts AutofillValue methods
- *  2. integrationg tests that uses a the InstrumentedAutofillService
- *
- *  The unit tests (createXxxx*() should either be moved to the CtsViewTestCases module or to a
- *  class that does not need to extend AutoFillServiceTestCase.
- *
- *  Most integration tests overlap the tests on CheckoutActivityTest - we should remove the
- *  redundant tests and add more tests (like triggering autofill using different views) to
- *  CheckoutActivityTest.
- */
-@AppModeFull(reason = "Unit test")
-public class AutofillValueTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<AllAutofillableViewsActivity> {
-
-    private AllAutofillableViewsActivity mActivity;
-    private EditText mEditText;
-    private CompoundButton mCompoundButton;
-    private RadioGroup mRadioGroup;
-    private RadioButton mRadioButton1;
-    private RadioButton mRadioButton2;
-    private Spinner mSpinner;
-    private DatePicker mDatePicker;
-    private TimePicker mTimePicker;
-
-    private void setFields(AllAutofillableViewsActivity activity) {
-        mActivity = activity;
-
-        mEditText = (EditText) mActivity.findViewById(R.id.editText);
-        mCompoundButton = (CompoundButton) mActivity.findViewById(R.id.compoundButton);
-        mRadioGroup = (RadioGroup) mActivity.findViewById(R.id.radioGroup);
-        mRadioButton1 = (RadioButton) mActivity.findViewById(R.id.radioButton1);
-        mRadioButton2 = (RadioButton) mActivity.findViewById(R.id.radioButton2);
-        mSpinner = (Spinner) mActivity.findViewById(R.id.spinner);
-        mDatePicker = (DatePicker) mActivity.findViewById(R.id.datePicker);
-        mTimePicker = (TimePicker) mActivity.findViewById(R.id.timePicker);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<AllAutofillableViewsActivity> getActivityRule() {
-        return new AutofillActivityTestRule<AllAutofillableViewsActivity>(
-                AllAutofillableViewsActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                setFields(getActivity());
-            }
-        };
-    }
-
-    @Test
-    public void createTextValue() throws Exception {
-        assertThat(AutofillValue.forText(null)).isNull();
-
-        assertThat(AutofillValue.forText("").isText()).isTrue();
-        assertThat(AutofillValue.forText("").isToggle()).isFalse();
-        assertThat(AutofillValue.forText("").isList()).isFalse();
-        assertThat(AutofillValue.forText("").isDate()).isFalse();
-
-        AutofillValue emptyV = AutofillValue.forText("");
-        assertThat(emptyV.getTextValue().toString()).isEqualTo("");
-
-        final AutofillValue v = AutofillValue.forText("someText");
-        assertThat(v.getTextValue()).isEqualTo("someText");
-
-        assertThrows(IllegalStateException.class, v::getToggleValue);
-        assertThrows(IllegalStateException.class, v::getListValue);
-        assertThrows(IllegalStateException.class, v::getDateValue);
-    }
-
-    @Test
-    public void createToggleValue() throws Exception {
-        assertThat(AutofillValue.forToggle(true).getToggleValue()).isTrue();
-        assertThat(AutofillValue.forToggle(false).getToggleValue()).isFalse();
-
-        assertThat(AutofillValue.forToggle(true).isText()).isFalse();
-        assertThat(AutofillValue.forToggle(true).isToggle()).isTrue();
-        assertThat(AutofillValue.forToggle(true).isList()).isFalse();
-        assertThat(AutofillValue.forToggle(true).isDate()).isFalse();
-
-
-        final AutofillValue v = AutofillValue.forToggle(true);
-
-        assertThrows(IllegalStateException.class, v::getTextValue);
-        assertThrows(IllegalStateException.class, v::getListValue);
-        assertThrows(IllegalStateException.class, v::getDateValue);
-    }
-
-    @Test
-    public void createListValue() throws Exception {
-        assertThat(AutofillValue.forList(-1).getListValue()).isEqualTo(-1);
-        assertThat(AutofillValue.forList(0).getListValue()).isEqualTo(0);
-        assertThat(AutofillValue.forList(1).getListValue()).isEqualTo(1);
-
-        assertThat(AutofillValue.forList(0).isText()).isFalse();
-        assertThat(AutofillValue.forList(0).isToggle()).isFalse();
-        assertThat(AutofillValue.forList(0).isList()).isTrue();
-        assertThat(AutofillValue.forList(0).isDate()).isFalse();
-
-        final AutofillValue v = AutofillValue.forList(0);
-
-        assertThrows(IllegalStateException.class, v::getTextValue);
-        assertThrows(IllegalStateException.class, v::getToggleValue);
-        assertThrows(IllegalStateException.class, v::getDateValue);
-    }
-
-    @Test
-    public void createDateValue() throws Exception {
-        assertThat(AutofillValue.forDate(-1).getDateValue()).isEqualTo(-1);
-        assertThat(AutofillValue.forDate(0).getDateValue()).isEqualTo(0);
-        assertThat(AutofillValue.forDate(1).getDateValue()).isEqualTo(1);
-
-        assertThat(AutofillValue.forDate(0).isText()).isFalse();
-        assertThat(AutofillValue.forDate(0).isToggle()).isFalse();
-        assertThat(AutofillValue.forDate(0).isList()).isFalse();
-        assertThat(AutofillValue.forDate(0).isDate()).isTrue();
-
-        final AutofillValue v = AutofillValue.forDate(0);
-
-        assertThrows(IllegalStateException.class, v::getTextValue);
-        assertThrows(IllegalStateException.class, v::getToggleValue);
-        assertThrows(IllegalStateException.class, v::getListValue);
-    }
-
-    /**
-     * Trigger autofill on a view.
-     *
-     * @param view The view to trigger the autofill on
-     */
-    private void startAutoFill(@NonNull View view) throws Exception {
-        mActivity.syncRunOnUiThread(() -> {
-            view.clearFocus();
-            view.requestFocus();
-        });
-
-        sReplier.getNextFillRequest();
-    }
-
-    private void autofillEditText(@Nullable AutofillValue value, String expectedText,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> mEditText.setVisibility(View.VISIBLE));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("editText", value)
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        OneTimeTextWatcher textWatcher = new OneTimeTextWatcher("editText", mEditText,
-                expectedText);
-        mEditText.addTextChangedListener(textWatcher);
-
-        // Trigger autofill.
-        startAutoFill(mEditText);
-
-        // Autofill it.
-        mUiBot.selectDataset("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            textWatcher.assertAutoFilled();
-        } else {
-            assertThat(mEditText.getText().toString()).isEqualTo(expectedText);
-        }
-    }
-
-    @Test
-    public void autofillValidTextValue() throws Exception {
-        autofillEditText(AutofillValue.forText("filled"), "filled", true);
-    }
-
-    @Test
-    public void autofillEmptyTextValue() throws Exception {
-        autofillEditText(AutofillValue.forText(""), "", true);
-    }
-
-    @Test
-    public void autofillTextWithListValue() throws Exception {
-        autofillEditText(AutofillValue.forList(0), "", false);
-    }
-
-    @Test
-    public void getEditTextAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> mEditText.setText("test"));
-        assertThat(mEditText.getAutofillValue()).isEqualTo(AutofillValue.forText("test"));
-
-        mActivity.syncRunOnUiThread(() -> mEditText.setEnabled(false));
-        assertThat(mEditText.getAutofillValue()).isNull();
-    }
-
-    private void autofillCompoundButton(@Nullable AutofillValue value, boolean expectedValue,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> mCompoundButton.setVisibility(View.VISIBLE));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("compoundButton", value)
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        OneTimeCompoundButtonListener checkedWatcher = new OneTimeCompoundButtonListener(
-                    "compoundButton", mCompoundButton, expectedValue);
-        mCompoundButton.setOnCheckedChangeListener(checkedWatcher);
-
-        startAutoFill(mCompoundButton);
-
-        // Autofill it.
-        mUiBot.selectDataset("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            checkedWatcher.assertAutoFilled();
-        } else {
-            assertThat(mCompoundButton.isChecked()).isEqualTo(expectedValue);
-        }
-    }
-
-    @Test
-    public void autofillToggleValueWithTrue() throws Exception {
-        autofillCompoundButton(AutofillValue.forToggle(true), true, true);
-    }
-
-    @Test
-    public void autofillToggleValueWithFalse() throws Exception {
-        autofillCompoundButton(AutofillValue.forToggle(false), false, false);
-    }
-
-    @Test
-    public void autofillCompoundButtonWithTextValue() throws Exception {
-        autofillCompoundButton(AutofillValue.forText(""), false, false);
-    }
-
-    @Test
-    public void getCompoundButtonAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> mCompoundButton.setChecked(true));
-        assertThat(mCompoundButton.getAutofillValue()).isEqualTo(AutofillValue.forToggle(true));
-
-        mActivity.syncRunOnUiThread(() -> mCompoundButton.setEnabled(false));
-        assertThat(mCompoundButton.getAutofillValue()).isNull();
-    }
-
-    private void autofillListValue(@Nullable AutofillValue value, int expectedValue,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> mSpinner.setVisibility(View.VISIBLE));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("spinner", value)
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        OneTimeSpinnerListener spinnerWatcher = new OneTimeSpinnerListener(
-                "spinner", mSpinner, expectedValue);
-        mSpinner.setOnItemSelectedListener(spinnerWatcher);
-
-        startAutoFill(mSpinner);
-
-        // Autofill it.
-        mUiBot.selectDatasetSync("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            spinnerWatcher.assertAutoFilled();
-        } else {
-            assertThat(mSpinner.getSelectedItemPosition()).isEqualTo(expectedValue);
-        }
-    }
-
-    @Test
-    public void autofillZeroListValueToSpinner() throws Exception {
-        autofillListValue(AutofillValue.forList(0), 0, false);
-    }
-
-    @Test
-    public void autofillOneListValueToSpinner() throws Exception {
-        autofillListValue(AutofillValue.forList(1), 1, true);
-    }
-
-    @Test
-    public void autofillInvalidListValueToSpinner() throws Exception {
-        autofillListValue(AutofillValue.forList(-1), 0, false);
-    }
-
-    @Test
-    public void autofillSpinnerWithTextValue() throws Exception {
-        autofillListValue(AutofillValue.forText(""), 0, false);
-    }
-
-    @Test
-    public void getSpinnerAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> mSpinner.setSelection(1));
-        assertThat(mSpinner.getAutofillValue()).isEqualTo(AutofillValue.forList(1));
-
-        mActivity.syncRunOnUiThread(() -> mSpinner.setEnabled(false));
-        assertThat(mSpinner.getAutofillValue()).isNull();
-    }
-
-    private void autofillDateValueToDatePicker(@Nullable AutofillValue value,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> {
-            mEditText.setVisibility(View.VISIBLE);
-            mDatePicker.setVisibility(View.VISIBLE);
-        });
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("datePicker", value)
-                .setField("editText", "filled")
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        OneTimeDateListener dateWatcher = new OneTimeDateListener("datePicker", mDatePicker,
-                2017, 3, 7);
-        mDatePicker.setOnDateChangedListener(dateWatcher);
-
-        int nonAutofilledYear = mDatePicker.getYear();
-        int nonAutofilledMonth = mDatePicker.getMonth();
-        int nonAutofilledDay = mDatePicker.getDayOfMonth();
-
-        // Trigger autofill.
-        startAutoFill(mEditText);
-
-        // Autofill it.
-        mUiBot.selectDataset("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            dateWatcher.assertAutoFilled();
-        } else {
-            Helper.assertDateValue(mDatePicker, nonAutofilledYear, nonAutofilledMonth,
-                    nonAutofilledDay);
-        }
-    }
-
-    private long getDateAsMillis(int year, int month, int day, int hour, int minute) {
-        Calendar calendar = Calendar.getInstance(
-                mActivity.getResources().getConfiguration().getLocales().get(0));
-
-        calendar.set(year, month, day, hour, minute);
-
-        return calendar.getTimeInMillis();
-    }
-
-    @Test
-    public void autofillValidDateValueToDatePicker() throws Exception {
-        autofillDateValueToDatePicker(AutofillValue.forDate(getDateAsMillis(2017, 3, 7, 12, 32)),
-                true);
-    }
-
-    @Test
-    public void autofillDatePickerWithTextValue() throws Exception {
-        autofillDateValueToDatePicker(AutofillValue.forText(""), false);
-    }
-
-    @Test
-    public void getDatePickerAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> mDatePicker.updateDate(2017, 3, 7));
-
-        Helper.assertDateValue(mDatePicker, 2017, 3, 7);
-
-        mActivity.syncRunOnUiThread(() -> mDatePicker.setEnabled(false));
-        assertThat(mDatePicker.getAutofillValue()).isNull();
-    }
-
-    private void autofillDateValueToTimePicker(@Nullable AutofillValue value,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> {
-            mEditText.setVisibility(View.VISIBLE);
-            mTimePicker.setIs24HourView(true);
-            mTimePicker.setVisibility(View.VISIBLE);
-        });
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("timePicker", value)
-                .setField("editText", "filled")
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        MultipleTimesTimeListener timeWatcher = new MultipleTimesTimeListener("timePicker", 1,
-                mTimePicker, 12, 32);
-        mTimePicker.setOnTimeChangedListener(timeWatcher);
-
-        int nonAutofilledHour = mTimePicker.getHour();
-        int nonAutofilledMinute = mTimePicker.getMinute();
-
-        // Trigger autofill.
-        startAutoFill(mEditText);
-
-        // Autofill it.
-        mUiBot.selectDataset("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            timeWatcher.assertAutoFilled();
-        } else {
-            Helper.assertTimeValue(mTimePicker, nonAutofilledHour, nonAutofilledMinute);
-        }
-    }
-
-    @Test
-    public void autofillValidDateValueToTimePicker() throws Exception {
-        autofillDateValueToTimePicker(AutofillValue.forDate(getDateAsMillis(2017, 3, 7, 12, 32)),
-                true);
-    }
-
-    @Test
-    public void autofillTimePickerWithTextValue() throws Exception {
-        autofillDateValueToTimePicker(AutofillValue.forText(""), false);
-    }
-
-    @Test
-    public void getTimePickerAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> {
-            mTimePicker.setHour(12);
-            mTimePicker.setMinute(32);
-        });
-
-        Helper.assertTimeValue(mTimePicker, 12, 32);
-
-        mActivity.syncRunOnUiThread(() -> mTimePicker.setEnabled(false));
-        assertThat(mTimePicker.getAutofillValue()).isNull();
-    }
-
-    private void autofillRadioGroup(@Nullable AutofillValue value, int expectedValue,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> mEditText.setVisibility(View.VISIBLE));
-        mActivity.syncRunOnUiThread(() -> mRadioGroup.setVisibility(View.VISIBLE));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("radioGroup", value)
-                .setField("editText", "filled")
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        MultipleTimesRadioGroupListener radioGroupWatcher = new MultipleTimesRadioGroupListener(
-                "radioGroup", 2, mRadioGroup, expectedValue);
-        mRadioGroup.setOnCheckedChangeListener(radioGroupWatcher);
-
-        // Trigger autofill.
-        startAutoFill(mEditText);
-
-        // Autofill it.
-        mUiBot.selectDataset("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            radioGroupWatcher.assertAutoFilled();
-        } else {
-            if (expectedValue == 0) {
-                assertThat(mRadioButton1.isChecked()).isEqualTo(true);
-                assertThat(mRadioButton2.isChecked()).isEqualTo(false);
-            } else {
-                assertThat(mRadioButton1.isChecked()).isEqualTo(false);
-                assertThat(mRadioButton2.isChecked()).isEqualTo(true);
-
-            }
-        }
-    }
-
-    @Test
-    public void autofillZeroListValueToRadioGroup() throws Exception {
-        autofillRadioGroup(AutofillValue.forList(0), 0, false);
-    }
-
-    @Test
-    public void autofillOneListValueToRadioGroup() throws Exception {
-        autofillRadioGroup(AutofillValue.forList(1), 1, true);
-    }
-
-    @Test
-    public void autofillInvalidListValueToRadioGroup() throws Exception {
-        autofillRadioGroup(AutofillValue.forList(-1), 0, false);
-    }
-
-    @Test
-    public void autofillRadioGroupWithTextValue() throws Exception {
-        autofillRadioGroup(AutofillValue.forText(""), 0, false);
-    }
-
-    @Test
-    public void getRadioGroupAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> mRadioButton2.setChecked(true));
-        assertThat(mRadioGroup.getAutofillValue()).isEqualTo(AutofillValue.forList(1));
-
-        mActivity.syncRunOnUiThread(() -> mRadioGroup.setEnabled(false));
-        assertThat(mRadioGroup.getAutofillValue()).isNull();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java
deleted file mode 100644
index 19a8ec1..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.os.CancellationSignal;
-import android.service.autofill.AutofillService;
-import android.service.autofill.FillCallback;
-import android.service.autofill.FillRequest;
-import android.service.autofill.SaveCallback;
-import android.service.autofill.SaveRequest;
-import android.util.Log;
-
-/**
- * An {@link AutofillService} implementation that does fails if called upon.
- */
-public class BadAutofillService extends AutofillService {
-
-    private static final String TAG = "BadAutofillService";
-
-    static final String SERVICE_NAME = BadAutofillService.class.getPackage().getName()
-            + "/." + BadAutofillService.class.getSimpleName();
-    static final String SERVICE_LABEL = "BadAutofillService";
-
-    @Override
-    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
-            FillCallback callback) {
-        Log.e(TAG, "onFillRequest() should never be called");
-        throw new UnsupportedOperationException("onFillRequest() should never be called");
-    }
-
-    @Override
-    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
-        Log.e(TAG, "onSaveRequest() should never be called");
-        throw new UnsupportedOperationException("onSaveRequest() should never be called");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java b/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java
deleted file mode 100644
index b063342..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.BatchUpdates;
-import android.service.autofill.InternalTransformation;
-import android.service.autofill.Transformation;
-import android.widget.RemoteViews;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class BatchUpdatesTest {
-
-    private final BatchUpdates.Builder mBuilder = new BatchUpdates.Builder();
-
-    @Test
-    public void testAddTransformation_null() {
-        assertThrows(IllegalArgumentException.class, () ->  mBuilder.transformChild(42, null));
-    }
-
-    @Test
-    public void testAddTransformation_invalidClass() {
-        assertThrows(IllegalArgumentException.class,
-                () ->  mBuilder.transformChild(42, mock(Transformation.class)));
-    }
-
-    @Test
-    public void testSetUpdateTemplate_null() {
-        assertThrows(NullPointerException.class, () ->  mBuilder.updateTemplate(null));
-    }
-
-    @Test
-    public void testEmptyObject() {
-        assertThrows(IllegalStateException.class, () ->  mBuilder.build());
-    }
-
-    @Test
-    public void testNoMoreChangesAfterBuild() {
-        assertThat(mBuilder.updateTemplate(mock(RemoteViews.class)).build()).isNotNull();
-        assertThrows(IllegalStateException.class,
-                () ->  mBuilder.updateTemplate(mock(RemoteViews.class)));
-        assertThrows(IllegalStateException.class,
-                () ->  mBuilder.transformChild(42, mock(InternalTransformation.class)));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
deleted file mode 100644
index 3fd6ce6..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ /dev/null
@@ -1,888 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.createInlinePresentation;
-import static android.autofillservice.cts.Helper.createPresentation;
-import static android.autofillservice.cts.Helper.getAutofillIds;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillCallback;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillResponse;
-import android.service.autofill.InlinePresentation;
-import android.service.autofill.SaveInfo;
-import android.service.autofill.UserData;
-import android.util.Log;
-import android.util.Pair;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.RemoteViews;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.regex.Pattern;
-
-/**
- * Helper class used to produce a {@link FillResponse} based on expected fields that should be
- * present in the {@link AssistStructure}.
- *
- * <p>Typical usage:
- *
- * <pre class="prettyprint">
- * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
- *               .addDataset(new CannedDataset.Builder("dataset_name")
- *                   .setField("resource_id1", AutofillValue.forText("value1"))
- *                   .setField("resource_id2", AutofillValue.forText("value2"))
- *                   .build())
- *               .build());
- * </pre class="prettyprint">
- */
-public final class CannedFillResponse {
-
-    private static final String TAG = CannedFillResponse.class.getSimpleName();
-
-    private final ResponseType mResponseType;
-    private final List<CannedDataset> mDatasets;
-    private final String mFailureMessage;
-    private final int mSaveType;
-    private final String[] mRequiredSavableIds;
-    private final String[] mOptionalSavableIds;
-    private final AutofillId[] mRequiredSavableAutofillIds;
-    private final CharSequence mSaveDescription;
-    private final Bundle mExtras;
-    private final RemoteViews mPresentation;
-    private final InlinePresentation mInlinePresentation;
-    private final RemoteViews mHeader;
-    private final RemoteViews mFooter;
-    private final IntentSender mAuthentication;
-    private final String[] mAuthenticationIds;
-    private final String[] mIgnoredIds;
-    private final int mNegativeActionStyle;
-    private final IntentSender mNegativeActionListener;
-    private final int mPositiveActionStyle;
-    private final int mSaveInfoFlags;
-    private final int mFillResponseFlags;
-    private final AutofillId mSaveTriggerId;
-    private final long mDisableDuration;
-    private final String[] mFieldClassificationIds;
-    private final boolean mFieldClassificationIdsOverflow;
-    private final SaveInfoDecorator mSaveInfoDecorator;
-    private final UserData mUserData;
-    private final DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
-    private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
-    private final int[] mCancelIds;
-
-    private CannedFillResponse(Builder builder) {
-        mResponseType = builder.mResponseType;
-        mDatasets = builder.mDatasets;
-        mFailureMessage = builder.mFailureMessage;
-        mRequiredSavableIds = builder.mRequiredSavableIds;
-        mRequiredSavableAutofillIds = builder.mRequiredSavableAutofillIds;
-        mOptionalSavableIds = builder.mOptionalSavableIds;
-        mSaveDescription = builder.mSaveDescription;
-        mSaveType = builder.mSaveType;
-        mExtras = builder.mExtras;
-        mPresentation = builder.mPresentation;
-        mInlinePresentation = builder.mInlinePresentation;
-        mHeader = builder.mHeader;
-        mFooter = builder.mFooter;
-        mAuthentication = builder.mAuthentication;
-        mAuthenticationIds = builder.mAuthenticationIds;
-        mIgnoredIds = builder.mIgnoredIds;
-        mNegativeActionStyle = builder.mNegativeActionStyle;
-        mNegativeActionListener = builder.mNegativeActionListener;
-        mPositiveActionStyle = builder.mPositiveActionStyle;
-        mSaveInfoFlags = builder.mSaveInfoFlags;
-        mFillResponseFlags = builder.mFillResponseFlags;
-        mSaveTriggerId = builder.mSaveTriggerId;
-        mDisableDuration = builder.mDisableDuration;
-        mFieldClassificationIds = builder.mFieldClassificationIds;
-        mFieldClassificationIdsOverflow = builder.mFieldClassificationIdsOverflow;
-        mSaveInfoDecorator = builder.mSaveInfoDecorator;
-        mUserData = builder.mUserData;
-        mVisitor = builder.mVisitor;
-        mSaveInfoVisitor = builder.mSaveInfoVisitor;
-        mCancelIds = builder.mCancelIds;
-    }
-
-    /**
-     * Constant used to pass a {@code null} response to the
-     * {@link FillCallback#onSuccess(FillResponse)} method.
-     */
-    public static final CannedFillResponse NO_RESPONSE =
-            new Builder(ResponseType.NULL).build();
-
-    /**
-     * Constant used to fail the test when an expected request was made.
-     */
-    public static final CannedFillResponse NO_MOAR_RESPONSES =
-            new Builder(ResponseType.NO_MORE).build();
-
-    /**
-     * Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
-     */
-    public static final CannedFillResponse DO_NOT_REPLY_RESPONSE =
-            new Builder(ResponseType.TIMEOUT).build();
-
-    /**
-     * Constant used to call {@link FillCallback#onFailure(CharSequence)} method.
-     */
-    public static final CannedFillResponse FAIL =
-            new Builder(ResponseType.FAILURE).build();
-
-    public String getFailureMessage() {
-        return mFailureMessage;
-    }
-
-    public ResponseType getResponseType() {
-        return mResponseType;
-    }
-
-    /**
-     * Creates a new response, replacing the dataset field ids by the real ids from the assist
-     * structure.
-     */
-    public FillResponse asFillResponse(@Nullable List<FillContext> contexts,
-            @NonNull Function<String, ViewNode> nodeResolver) {
-        final FillResponse.Builder builder = new FillResponse.Builder()
-                .setFlags(mFillResponseFlags);
-        if (mDatasets != null) {
-            for (CannedDataset cannedDataset : mDatasets) {
-                final Dataset dataset = cannedDataset.asDataset(nodeResolver);
-                assertWithMessage("Cannot create datase").that(dataset).isNotNull();
-                builder.addDataset(dataset);
-            }
-        }
-        final SaveInfo.Builder saveInfoBuilder;
-        if (mRequiredSavableIds != null || mOptionalSavableIds != null
-                || mRequiredSavableAutofillIds != null || mSaveInfoDecorator != null) {
-            if (mRequiredSavableAutofillIds != null) {
-                saveInfoBuilder = new SaveInfo.Builder(mSaveType, mRequiredSavableAutofillIds);
-            } else {
-                saveInfoBuilder = mRequiredSavableIds == null || mRequiredSavableIds.length == 0
-                        ? new SaveInfo.Builder(mSaveType)
-                            : new SaveInfo.Builder(mSaveType,
-                                    getAutofillIds(nodeResolver, mRequiredSavableIds));
-            }
-
-            saveInfoBuilder.setFlags(mSaveInfoFlags);
-
-            if (mOptionalSavableIds != null) {
-                saveInfoBuilder.setOptionalIds(getAutofillIds(nodeResolver, mOptionalSavableIds));
-            }
-            if (mSaveDescription != null) {
-                saveInfoBuilder.setDescription(mSaveDescription);
-            }
-            if (mNegativeActionListener != null) {
-                saveInfoBuilder.setNegativeAction(mNegativeActionStyle, mNegativeActionListener);
-            }
-
-            saveInfoBuilder.setPositiveAction(mPositiveActionStyle);
-
-            if (mSaveTriggerId != null) {
-                saveInfoBuilder.setTriggerId(mSaveTriggerId);
-            }
-        } else if (mSaveInfoFlags != 0) {
-            saveInfoBuilder = new SaveInfo.Builder(mSaveType).setFlags(mSaveInfoFlags);
-        } else {
-            saveInfoBuilder = null;
-        }
-        if (saveInfoBuilder != null) {
-            // TODO: merge decorator and visitor
-            if (mSaveInfoDecorator != null) {
-                mSaveInfoDecorator.decorate(saveInfoBuilder, nodeResolver);
-            }
-            if (mSaveInfoVisitor != null) {
-                Log.d(TAG, "Visiting saveInfo " + saveInfoBuilder);
-                mSaveInfoVisitor.visit(contexts, saveInfoBuilder);
-            }
-            final SaveInfo saveInfo = saveInfoBuilder.build();
-            Log.d(TAG, "saveInfo:" + saveInfo);
-            builder.setSaveInfo(saveInfo);
-        }
-        if (mIgnoredIds != null) {
-            builder.setIgnoredIds(getAutofillIds(nodeResolver, mIgnoredIds));
-        }
-        if (mAuthenticationIds != null) {
-            builder.setAuthentication(getAutofillIds(nodeResolver, mAuthenticationIds),
-                    mAuthentication, mPresentation, mInlinePresentation);
-        }
-        if (mDisableDuration > 0) {
-            builder.disableAutofill(mDisableDuration);
-        }
-        if (mFieldClassificationIdsOverflow) {
-            final int length = UserData.getMaxFieldClassificationIdsSize() + 1;
-            final AutofillId[] fieldIds = new AutofillId[length];
-            for (int i = 0; i < length; i++) {
-                fieldIds[i] = new AutofillId(i);
-            }
-            builder.setFieldClassificationIds(fieldIds);
-        } else if (mFieldClassificationIds != null) {
-            builder.setFieldClassificationIds(
-                    getAutofillIds(nodeResolver, mFieldClassificationIds));
-        }
-        if (mExtras != null) {
-            builder.setClientState(mExtras);
-        }
-        if (mHeader != null) {
-            builder.setHeader(mHeader);
-        }
-        if (mFooter != null) {
-            builder.setFooter(mFooter);
-        }
-        if (mUserData != null) {
-            builder.setUserData(mUserData);
-        }
-        if (mVisitor != null) {
-            Log.d(TAG, "Visiting " + builder);
-            mVisitor.visit(contexts, builder);
-        }
-        builder.setPresentationCancelIds(mCancelIds);
-
-        final FillResponse response = builder.build();
-        Log.v(TAG, "Response: " + response);
-        return response;
-    }
-
-    @Override
-    public String toString() {
-        return "CannedFillResponse: [type=" + mResponseType
-                + ",datasets=" + mDatasets
-                + ", requiredSavableIds=" + Arrays.toString(mRequiredSavableIds)
-                + ", optionalSavableIds=" + Arrays.toString(mOptionalSavableIds)
-                + ", requiredSavableAutofillIds=" + Arrays.toString(mRequiredSavableAutofillIds)
-                + ", saveInfoFlags=" + mSaveInfoFlags
-                + ", fillResponseFlags=" + mFillResponseFlags
-                + ", failureMessage=" + mFailureMessage
-                + ", saveDescription=" + mSaveDescription
-                + ", hasPresentation=" + (mPresentation != null)
-                + ", hasInlinePresentation=" + (mInlinePresentation != null)
-                + ", hasHeader=" + (mHeader != null)
-                + ", hasFooter=" + (mFooter != null)
-                + ", hasAuthentication=" + (mAuthentication != null)
-                + ", authenticationIds=" + Arrays.toString(mAuthenticationIds)
-                + ", ignoredIds=" + Arrays.toString(mIgnoredIds)
-                + ", saveTriggerId=" + mSaveTriggerId
-                + ", disableDuration=" + mDisableDuration
-                + ", fieldClassificationIds=" + Arrays.toString(mFieldClassificationIds)
-                + ", fieldClassificationIdsOverflow=" + mFieldClassificationIdsOverflow
-                + ", saveInfoDecorator=" + mSaveInfoDecorator
-                + ", userData=" + mUserData
-                + ", visitor=" + mVisitor
-                + ", saveInfoVisitor=" + mSaveInfoVisitor
-                + "]";
-    }
-
-    public enum ResponseType {
-        NORMAL,
-        NULL,
-        NO_MORE,
-        TIMEOUT,
-        FAILURE,
-        DELAY
-    }
-
-    public static final class Builder {
-        private final List<CannedDataset> mDatasets = new ArrayList<>();
-        private final ResponseType mResponseType;
-        private String mFailureMessage;
-        private String[] mRequiredSavableIds;
-        private String[] mOptionalSavableIds;
-        private AutofillId[] mRequiredSavableAutofillIds;
-        private CharSequence mSaveDescription;
-        public int mSaveType = -1;
-        private Bundle mExtras;
-        private RemoteViews mPresentation;
-        private InlinePresentation mInlinePresentation;
-        private RemoteViews mFooter;
-        private RemoteViews mHeader;
-        private IntentSender mAuthentication;
-        private String[] mAuthenticationIds;
-        private String[] mIgnoredIds;
-        private int mNegativeActionStyle;
-        private IntentSender mNegativeActionListener;
-        private int mPositiveActionStyle;
-        private int mSaveInfoFlags;
-        private int mFillResponseFlags;
-        private AutofillId mSaveTriggerId;
-        private long mDisableDuration;
-        private String[] mFieldClassificationIds;
-        private boolean mFieldClassificationIdsOverflow;
-        private SaveInfoDecorator mSaveInfoDecorator;
-        private UserData mUserData;
-        private DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
-        private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
-        private int[] mCancelIds;
-
-        public Builder(ResponseType type) {
-            mResponseType = type;
-        }
-
-        public Builder() {
-            this(ResponseType.NORMAL);
-        }
-
-        public Builder addDataset(CannedDataset dataset) {
-            assertWithMessage("already set failure").that(mFailureMessage).isNull();
-            mDatasets.add(dataset);
-            return this;
-        }
-
-        /**
-         * Sets the required savable ids based on their {@code resourceId}.
-         */
-        public Builder setRequiredSavableIds(int type, String... ids) {
-            mSaveType = type;
-            mRequiredSavableIds = ids;
-            return this;
-        }
-
-        public Builder setSaveInfoFlags(int flags) {
-            mSaveInfoFlags = flags;
-            return this;
-        }
-
-        public Builder setFillResponseFlags(int flags) {
-            mFillResponseFlags = flags;
-            return this;
-        }
-
-        /**
-         * Sets the optional savable ids based on they {@code resourceId}.
-         */
-        public Builder setOptionalSavableIds(String... ids) {
-            mOptionalSavableIds = ids;
-            return this;
-        }
-
-        /**
-         * Sets the description passed to the {@link SaveInfo}.
-         */
-        public Builder setSaveDescription(CharSequence description) {
-            mSaveDescription = description;
-            return this;
-        }
-
-        /**
-         * Sets the extra passed to {@link
-         * android.service.autofill.FillResponse.Builder#setClientState(Bundle)}.
-         */
-        public Builder setExtras(Bundle data) {
-            mExtras = data;
-            return this;
-        }
-
-        /**
-         * Sets the view to present the response in the UI.
-         */
-        public Builder setPresentation(RemoteViews presentation) {
-            mPresentation = presentation;
-            return this;
-        }
-
-        /**
-         * Sets the view to present the response in the UI.
-         */
-        public Builder setInlinePresentation(InlinePresentation inlinePresentation) {
-            mInlinePresentation = inlinePresentation;
-            return this;
-        }
-
-        /**
-         * Sets views to present the response in the UI by the type.
-         */
-        public Builder setPresentation(String message, boolean inlineMode) {
-            mPresentation = createPresentation(message);
-            if (inlineMode) {
-                mInlinePresentation = createInlinePresentation(message);
-            }
-            return this;
-        }
-
-        /**
-         * Sets the authentication intent.
-         */
-        public Builder setAuthentication(IntentSender authentication, String... ids) {
-            mAuthenticationIds = ids;
-            mAuthentication = authentication;
-            return this;
-        }
-
-        /**
-         * Sets the ignored fields based on resource ids.
-         */
-        public Builder setIgnoreFields(String...ids) {
-            mIgnoredIds = ids;
-            return this;
-        }
-
-        /**
-         * Sets the negative action spec.
-         */
-        public Builder setNegativeAction(int style, IntentSender listener) {
-            mNegativeActionStyle = style;
-            mNegativeActionListener = listener;
-            return this;
-        }
-
-        /**
-         * Sets the positive action spec.
-         */
-        public Builder setPositiveAction(int style) {
-            mPositiveActionStyle = style;
-            return this;
-        }
-
-        public CannedFillResponse build() {
-            return new CannedFillResponse(this);
-        }
-
-        /**
-         * Sets the response to call {@link FillCallback#onFailure(CharSequence)}.
-         */
-        public Builder returnFailure(String message) {
-            assertWithMessage("already added datasets").that(mDatasets).isEmpty();
-            mFailureMessage = message;
-            return this;
-        }
-
-        /**
-         * Sets the view that explicitly triggers save.
-         */
-        public Builder setSaveTriggerId(AutofillId id) {
-            assertWithMessage("already set").that(mSaveTriggerId).isNull();
-            mSaveTriggerId = id;
-            return this;
-        }
-
-        public Builder disableAutofill(long duration) {
-            assertWithMessage("already set").that(mDisableDuration).isEqualTo(0L);
-            mDisableDuration = duration;
-            return this;
-        }
-
-        /**
-         * Sets the ids used for field classification.
-         */
-        public Builder setFieldClassificationIds(String... ids) {
-            assertWithMessage("already set").that(mFieldClassificationIds).isNull();
-            mFieldClassificationIds = ids;
-            return this;
-        }
-
-        /**
-         * Forces the service to throw an exception when setting the fields classification ids.
-         */
-        public Builder setFieldClassificationIdsOverflow() {
-            mFieldClassificationIdsOverflow = true;
-            return this;
-        }
-
-        public Builder setHeader(RemoteViews header) {
-            assertWithMessage("already set").that(mHeader).isNull();
-            mHeader = header;
-            return this;
-        }
-
-        public Builder setFooter(RemoteViews footer) {
-            assertWithMessage("already set").that(mFooter).isNull();
-            mFooter = footer;
-            return this;
-        }
-
-        public Builder setSaveInfoDecorator(SaveInfoDecorator decorator) {
-            assertWithMessage("already set").that(mSaveInfoDecorator).isNull();
-            mSaveInfoDecorator = decorator;
-            return this;
-        }
-
-        /**
-         * Sets the package-specific UserData.
-         *
-         * <p>Overrides the default UserData for field classification.
-         */
-        public Builder setUserData(UserData userData) {
-            assertWithMessage("already set").that(mUserData).isNull();
-            mUserData = userData;
-            return this;
-        }
-
-        /**
-         * Sets a generic visitor for the "real" request and response.
-         *
-         * <p>Typically used in cases where the test need to infer data from the request to build
-         * the response.
-         */
-        public Builder setVisitor(
-                @NonNull DoubleVisitor<List<FillContext>, FillResponse.Builder> visitor) {
-            mVisitor = visitor;
-            return this;
-        }
-
-        /**
-         * Sets a generic visitor for the "real" request and save info.
-         *
-         * <p>Typically used in cases where the test need to infer data from the request to build
-         * the response.
-         */
-        public Builder setSaveInfoVisitor(
-                @NonNull DoubleVisitor<List<FillContext>, SaveInfo.Builder> visitor) {
-            mSaveInfoVisitor = visitor;
-            return this;
-        }
-
-        /**
-         * Sets targets that cancel current session
-         */
-        public Builder setPresentationCancelIds(int[] ids) {
-            mCancelIds = ids;
-            return this;
-        }
-    }
-
-    /**
-     * Helper class used to produce a {@link Dataset} based on expected fields that should be
-     * present in the {@link AssistStructure}.
-     *
-     * <p>Typical usage:
-     *
-     * <pre class="prettyprint">
-     * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
-     *               .addDataset(new CannedDataset.Builder("dataset_name")
-     *                   .setField("resource_id1", AutofillValue.forText("value1"))
-     *                   .setField("resource_id2", AutofillValue.forText("value2"))
-     *                   .build())
-     *               .build());
-     * </pre class="prettyprint">
-     */
-    public static class CannedDataset {
-        private final Map<String, AutofillValue> mFieldValues;
-        private final Map<String, RemoteViews> mFieldPresentations;
-        private final Map<String, InlinePresentation> mFieldInlinePresentations;
-        private final Map<String, Pair<Boolean, Pattern>> mFieldFilters;
-        private final RemoteViews mPresentation;
-        private final InlinePresentation mInlinePresentation;
-        private final IntentSender mAuthentication;
-        private final String mId;
-
-        private CannedDataset(Builder builder) {
-            mFieldValues = builder.mFieldValues;
-            mFieldPresentations = builder.mFieldPresentations;
-            mFieldInlinePresentations = builder.mFieldInlinePresentations;
-            mFieldFilters = builder.mFieldFilters;
-            mPresentation = builder.mPresentation;
-            mInlinePresentation = builder.mInlinePresentation;
-            mAuthentication = builder.mAuthentication;
-            mId = builder.mId;
-        }
-
-        /**
-         * Creates a new dataset, replacing the field ids by the real ids from the assist structure.
-         */
-        Dataset asDataset(Function<String, ViewNode> nodeResolver) {
-            final Dataset.Builder builder = mPresentation != null
-                    ? mInlinePresentation == null
-                    ? new Dataset.Builder(mPresentation)
-                    : new Dataset.Builder(mPresentation).setInlinePresentation(mInlinePresentation)
-                    : mInlinePresentation == null
-                            ? new Dataset.Builder()
-                            : new Dataset.Builder(mInlinePresentation);
-
-            if (mFieldValues != null) {
-                for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
-                    final String id = entry.getKey();
-                    final ViewNode node = nodeResolver.apply(id);
-                    if (node == null) {
-                        throw new AssertionError("No node with resource id " + id);
-                    }
-                    final AutofillId autofillId = node.getAutofillId();
-                    final AutofillValue value = entry.getValue();
-                    final RemoteViews presentation = mFieldPresentations.get(id);
-                    final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(id);
-                    final Pair<Boolean, Pattern> filter = mFieldFilters.get(id);
-                    if (presentation != null) {
-                        if (filter == null) {
-                            if (inlinePresentation != null) {
-                                builder.setValue(autofillId, value, presentation,
-                                        inlinePresentation);
-                            } else {
-                                builder.setValue(autofillId, value, presentation);
-                            }
-                        } else {
-                            if (inlinePresentation != null) {
-                                builder.setValue(autofillId, value, filter.second, presentation,
-                                        inlinePresentation);
-                            } else {
-                                builder.setValue(autofillId, value, filter.second, presentation);
-                            }
-                        }
-                    } else {
-                        if (inlinePresentation != null) {
-                            builder.setFieldInlinePresentation(autofillId, value,
-                                    filter != null ? filter.second : null, inlinePresentation);
-                        } else {
-                            if (filter == null) {
-                                builder.setValue(autofillId, value);
-                            } else {
-                                builder.setValue(autofillId, value, filter.second);
-                            }
-                        }
-                    }
-                }
-            }
-            builder.setId(mId).setAuthentication(mAuthentication);
-            return builder.build();
-        }
-
-        @Override
-        public String toString() {
-            return "CannedDataset " + mId + " : [hasPresentation=" + (mPresentation != null)
-                    + ", hasInlinePresentation=" + (mInlinePresentation != null)
-                    + ", fieldPresentations=" + (mFieldPresentations)
-                    + ", fieldInlinePresentations=" + (mFieldInlinePresentations)
-                    + ", hasAuthentication=" + (mAuthentication != null)
-                    + ", fieldValues=" + mFieldValues
-                    + ", fieldFilters=" + mFieldFilters + "]";
-        }
-
-        public static class Builder {
-            private final Map<String, AutofillValue> mFieldValues = new HashMap<>();
-            private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
-            private final Map<String, InlinePresentation> mFieldInlinePresentations =
-                    new HashMap<>();
-            private final Map<String, Pair<Boolean, Pattern>> mFieldFilters = new HashMap<>();
-
-            private RemoteViews mPresentation;
-            private InlinePresentation mInlinePresentation;
-            private IntentSender mAuthentication;
-            private String mId;
-
-            public Builder() {
-
-            }
-
-            public Builder(RemoteViews presentation) {
-                mPresentation = presentation;
-            }
-
-            /**
-             * Sets the canned value of a text field based on its {@code id}.
-             *
-             * <p>The meaning of the id is defined by the object using the canned dataset.
-             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
-             * {@link IdMode}.
-             */
-            public Builder setField(String id, String text) {
-                return setField(id, AutofillValue.forText(text));
-            }
-
-            /**
-             * Sets the canned value of a text field based on its {@code id}.
-             *
-             * <p>The meaning of the id is defined by the object using the canned dataset.
-             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
-             * {@link IdMode}.
-             */
-            public Builder setField(String id, String text, Pattern filter) {
-                return setField(id, AutofillValue.forText(text), true, filter);
-            }
-
-            public Builder setUnfilterableField(String id, String text) {
-                return setField(id, AutofillValue.forText(text), false, null);
-            }
-
-            /**
-             * Sets the canned value of a list field based on its its {@code id}.
-             *
-             * <p>The meaning of the id is defined by the object using the canned dataset.
-             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
-             * {@link IdMode}.
-             */
-            public Builder setField(String id, int index) {
-                return setField(id, AutofillValue.forList(index));
-            }
-
-            /**
-             * Sets the canned value of a toggle field based on its {@code id}.
-             *
-             * <p>The meaning of the id is defined by the object using the canned dataset.
-             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
-             * {@link IdMode}.
-             */
-            public Builder setField(String id, boolean toggled) {
-                return setField(id, AutofillValue.forToggle(toggled));
-            }
-
-            /**
-             * Sets the canned value of a date field based on its {@code id}.
-             *
-             * <p>The meaning of the id is defined by the object using the canned dataset.
-             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
-             * {@link IdMode}.
-             */
-            public Builder setField(String id, long date) {
-                return setField(id, AutofillValue.forDate(date));
-            }
-
-            /**
-             * Sets the canned value of a date field based on its {@code id}.
-             *
-             * <p>The meaning of the id is defined by the object using the canned dataset.
-             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
-             * {@link IdMode}.
-             */
-            public Builder setField(String id, AutofillValue value) {
-                mFieldValues.put(id, value);
-                return this;
-            }
-
-            /**
-             * Sets the canned value of a date field based on its {@code id}.
-             *
-             * <p>The meaning of the id is defined by the object using the canned dataset.
-             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
-             * {@link IdMode}.
-             */
-            public Builder setField(String id, AutofillValue value, boolean filterable,
-                    Pattern filter) {
-                setField(id, value);
-                mFieldFilters.put(id, new Pair<>(filterable, filter));
-                return this;
-            }
-
-            /**
-             * Sets the canned value of a field based on its {@code id}.
-             *
-             * <p>The meaning of the id is defined by the object using the canned dataset.
-             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
-             * {@link IdMode}.
-             */
-            public Builder setField(String id, String text, RemoteViews presentation) {
-                setField(id, text);
-                mFieldPresentations.put(id, presentation);
-                return this;
-            }
-
-            /**
-             * Sets the canned value of a field based on its {@code id}.
-             *
-             * <p>The meaning of the id is defined by the object using the canned dataset.
-             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
-             * {@link IdMode}.
-             */
-            public Builder setField(String id, String text, RemoteViews presentation,
-                    Pattern filter) {
-                setField(id, text, presentation);
-                mFieldFilters.put(id, new Pair<>(true, filter));
-                return this;
-            }
-
-            /**
-             * Sets the canned value of a field based on its {@code id}.
-             *
-             * <p>The meaning of the id is defined by the object using the canned dataset.
-             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
-             * {@link IdMode}.
-             */
-            public Builder setField(String id, String text, RemoteViews presentation,
-                    InlinePresentation inlinePresentation) {
-                setField(id, text);
-                mFieldPresentations.put(id, presentation);
-                mFieldInlinePresentations.put(id, inlinePresentation);
-                return this;
-            }
-
-            /**
-             * Sets the canned value of a field based on its {@code id}.
-             *
-             * <p>The meaning of the id is defined by the object using the canned dataset.
-             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
-             * {@link IdMode}.
-             */
-            public Builder setField(String id, String text, RemoteViews presentation,
-                    InlinePresentation inlinePresentation, Pattern filter) {
-                setField(id, text, presentation, inlinePresentation);
-                mFieldFilters.put(id, new Pair<>(true, filter));
-                return this;
-            }
-
-            /**
-             * Sets the view to present the response in the UI.
-             */
-            public Builder setPresentation(RemoteViews presentation) {
-                mPresentation = presentation;
-                return this;
-            }
-
-            /**
-             * Sets the view to present the response in the UI.
-             */
-            public Builder setInlinePresentation(InlinePresentation inlinePresentation) {
-                mInlinePresentation = inlinePresentation;
-                return this;
-            }
-
-            public Builder setPresentation(String message, boolean inlineMode) {
-                mPresentation = createPresentation(message);
-                if (inlineMode) {
-                    mInlinePresentation = createInlinePresentation(message);
-                }
-                return this;
-            }
-
-            /**
-             * Sets the authentication intent.
-             */
-            public Builder setAuthentication(IntentSender authentication) {
-                mAuthentication = authentication;
-                return this;
-            }
-
-            /**
-             * Sets the name.
-             */
-            public Builder setId(String id) {
-                mId = id;
-                return this;
-            }
-
-            /**
-             * Builds the canned dataset.
-             */
-            public CannedDataset build() {
-                return new CannedDataset(this);
-            }
-        }
-    }
-
-    interface SaveInfoDecorator {
-        void decorate(SaveInfo.Builder builder, Function<String, ViewNode> nodeResolver);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceMatcher.java b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceMatcher.java
deleted file mode 100644
index d3a0404..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceMatcher.java
+++ /dev/null
@@ -1,36 +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.autofillservice.cts;
-
-import org.mockito.ArgumentMatcher;
-
-final class CharSequenceMatcher implements ArgumentMatcher<CharSequence> {
-    private final CharSequence mExpected;
-
-    CharSequenceMatcher(CharSequence expected) {
-        mExpected = expected;
-    }
-
-    @Override
-    public boolean matches(CharSequence actual) {
-        return actual.toString().equals(mExpected.toString());
-    }
-
-    @Override
-    public String toString() {
-        return mExpected.toString();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
deleted file mode 100644
index 73fc9d4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.ValueFinder;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.regex.Pattern;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class CharSequenceTransformationTest {
-
-    @Test
-    public void testAllNullBuilder() {
-        assertThrows(NullPointerException.class,
-                () ->  new CharSequenceTransformation.Builder(null, null, null));
-    }
-
-    @Test
-    public void testNullAutofillIdBuilder() {
-        assertThrows(NullPointerException.class,
-                () -> new CharSequenceTransformation.Builder(null, Pattern.compile(""), ""));
-    }
-
-    @Test
-    public void testNullRegexBuilder() {
-        assertThrows(NullPointerException.class,
-                () -> new CharSequenceTransformation.Builder(new AutofillId(1), null, ""));
-    }
-
-    @Test
-    public void testNullSubstBuilder() {
-        assertThrows(NullPointerException.class,
-                () -> new CharSequenceTransformation.Builder(new AutofillId(1), Pattern.compile(""),
-                        null));
-    }
-
-    @Test
-    public void testBadSubst() {
-        AutofillId id1 = new AutofillId(1);
-        AutofillId id2 = new AutofillId(2);
-        AutofillId id3 = new AutofillId(3);
-        AutofillId id4 = new AutofillId(4);
-
-        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
-                Pattern.compile("(.)"), "1=$1");
-
-        // bad subst: The regex has no capture groups
-        b.addField(id2, Pattern.compile("."), "2=$1");
-
-        // bad subst: The regex does not have enough capture groups
-        b.addField(id3, Pattern.compile("(.)"), "3=$2");
-
-        b.addField(id4, Pattern.compile("(.)"), "4=$1");
-
-        CharSequenceTransformation trans = b.build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("a");
-        when(finder.findByAutofillId(id2)).thenReturn("b");
-        when(finder.findByAutofillId(id3)).thenReturn("c");
-        when(finder.findByAutofillId(id4)).thenReturn("d");
-
-        assertThrows(IndexOutOfBoundsException.class, () -> trans.apply(finder, template, 0));
-
-        // fail one, fail all
-        verify(template, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void testUnknownField() throws Exception {
-        AutofillId id1 = new AutofillId(1);
-        AutofillId id2 = new AutofillId(2);
-        AutofillId unknownId = new AutofillId(42);
-
-        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
-                Pattern.compile(".*"), "1");
-
-        // bad subst: The field will not be found
-        b.addField(unknownId, Pattern.compile(".*"), "unknown");
-
-        b.addField(id2, Pattern.compile(".*"), "2");
-
-        CharSequenceTransformation trans = b.build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("1");
-        when(finder.findByAutofillId(id2)).thenReturn("2");
-        when(finder.findByAutofillId(unknownId)).thenReturn(null);
-
-        trans.apply(finder, template, 0);
-
-        // if a view cannot be found, nothing is not, not even partial results
-        verify(template, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void testCreditCardObfuscator() throws Exception {
-        AutofillId creditCardFieldId = new AutofillId(1);
-        CharSequenceTransformation trans = new CharSequenceTransformation
-                .Builder(creditCardFieldId,
-                        Pattern.compile("^\\s*\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?(\\d{4})\\s*$"),
-                        "...$1")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("1234 5678 9012 3456");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("...3456")));
-    }
-
-    @Test
-    public void testReplaceAllByOne() throws Exception {
-        AutofillId id = new AutofillId(1);
-        CharSequenceTransformation trans = new CharSequenceTransformation
-                .Builder(id, Pattern.compile("."), "*")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("four");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("****")));
-    }
-
-    @Test
-    public void testPartialMatchIsIgnored() throws Exception {
-        AutofillId id = new AutofillId(1);
-        CharSequenceTransformation trans = new CharSequenceTransformation
-                .Builder(id, Pattern.compile("^MATCH$"), "*")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("preMATCHpost");
-
-        trans.apply(finder, template, 0);
-
-        verify(template, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void userNameObfuscator() throws Exception {
-        AutofillId userNameFieldId = new AutofillId(1);
-        AutofillId passwordFieldId = new AutofillId(2);
-        CharSequenceTransformation trans = new CharSequenceTransformation
-                .Builder(userNameFieldId, Pattern.compile("(.*)"), "$1")
-                .addField(passwordFieldId, Pattern.compile(".*(..)$"), "/..$1")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(userNameFieldId)).thenReturn("myUserName");
-        when(finder.findByAutofillId(passwordFieldId)).thenReturn("myPassword");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setCharSequence(eq(0), any(),
-                argThat(new CharSequenceMatcher("myUserName/..rd")));
-    }
-
-    @Test
-    public void testMismatch() throws Exception {
-        AutofillId id1 = new AutofillId(1);
-        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
-                Pattern.compile("Who are you?"), "1");
-
-        CharSequenceTransformation trans = b.build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("I'm Batman!");
-
-        trans.apply(finder, template, 0);
-
-        // If the match fails, the view should not change.
-        verify(template, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void testFieldsAreAppliedInOrder() throws Exception {
-        AutofillId id1 = new AutofillId(1);
-        AutofillId id2 = new AutofillId(2);
-        AutofillId id3 = new AutofillId(3);
-        CharSequenceTransformation trans = new CharSequenceTransformation
-                .Builder(id1, Pattern.compile("a"), "A")
-                .addField(id3, Pattern.compile("c"), "C")
-                .addField(id2, Pattern.compile("b"), "B")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("a");
-        when(finder.findByAutofillId(id2)).thenReturn("b");
-        when(finder.findByAutofillId(id3)).thenReturn("c");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("ACB")));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
deleted file mode 100644
index f5e7f87..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.widget.ArrayAdapter.createFromResource;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.Spinner;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity that has the following fields:
- *
- * <ul>
- *   <li>Credit Card Number EditText (id: cc_numberusername, no input-type)
- *   <li>Credit Card Expiration EditText (id: cc_expiration, no input-type)
- *   <li>Address RadioGroup (id: addess, no autofill-type)
- *   <li>Save Credit Card CheckBox (id: save_cc, no autofill-type)
- *   <li>Clear Button
- *   <li>Buy Button
- * </ul>
- */
-public class CheckoutActivity extends AbstractAutoFillActivity {
-    private static final long BUY_TIMEOUT_MS = 1000;
-
-    static final String ID_CC_NUMBER = "cc_number";
-    static final String ID_CC_EXPIRATION = "cc_expiration";
-    static final String ID_ADDRESS = "address";
-    static final String ID_HOME_ADDRESS = "home_address";
-    static final String ID_WORK_ADDRESS = "work_address";
-    static final String ID_SAVE_CC = "save_cc";
-
-    static final int INDEX_ADDRESS_HOME = 0;
-    static final int INDEX_ADDRESS_WORK = 1;
-
-    static final int INDEX_CC_EXPIRATION_YESTERDAY = 0;
-    static final int INDEX_CC_EXPIRATION_TODAY = 1;
-    static final int INDEX_CC_EXPIRATION_TOMORROW = 2;
-    static final int INDEX_CC_EXPIRATION_NEVER = 3;
-
-    private EditText mCcNumber;
-    private Spinner mCcExpiration;
-    private ArrayAdapter<CharSequence> mCcExpirationAdapter;
-    private RadioGroup mAddress;
-    private RadioButton mHomeAddress;
-    private CheckBox mSaveCc;
-    private Button mBuyButton;
-    private Button mClearButton;
-
-    private FillExpectation mExpectation;
-    private CountDownLatch mBuyLatch;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(getContentView());
-
-        mCcNumber = findViewById(R.id.cc_number);
-        mCcExpiration = findViewById(R.id.cc_expiration);
-        mAddress = findViewById(R.id.address);
-        mHomeAddress = findViewById(R.id.home_address);
-        mSaveCc = findViewById(R.id.save_cc);
-        mBuyButton = findViewById(R.id.buy);
-        mClearButton = findViewById(R.id.clear);
-
-        mCcExpirationAdapter = createFromResource(this,
-                R.array.cc_expiration_values, android.R.layout.simple_spinner_item);
-        mCcExpirationAdapter
-                .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-        mCcExpiration.setAdapter(mCcExpirationAdapter);
-
-        mBuyButton.setOnClickListener((v) -> buy());
-        mClearButton.setOnClickListener((v) -> resetFields());
-    }
-
-    protected int getContentView() {
-        return R.layout.checkout_activity;
-    }
-
-    /**
-     * Resets the values of the input fields.
-     */
-    private void resetFields() {
-        mCcNumber.setText("");
-        mCcExpiration.setSelection(0, false);
-        mAddress.clearCheck();
-        mSaveCc.setChecked(false);
-    }
-
-    /**
-     * Emulates a buy action.
-     */
-    private void buy() {
-        final Intent intent = new Intent(this, WelcomeActivity.class);
-        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Thank you an come again!");
-        startActivity(intent);
-        if (mBuyLatch != null) {
-            // Latch is not set when activity launched outside tests
-            mBuyLatch.countDown();
-        }
-        finish();
-    }
-
-    /**
-     * Sets the expectation for an auto-fill request, so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    void expectAutoFill(String ccNumber, int ccExpirationIndex, int addressId, boolean saveCc) {
-        mExpectation = new FillExpectation(ccNumber, ccExpirationIndex, addressId, saveCc);
-        mCcNumber.addTextChangedListener(mExpectation.ccNumberWatcher);
-        mCcExpiration.setOnItemSelectedListener(mExpectation.ccExpirationListener);
-        mAddress.setOnCheckedChangeListener(mExpectation.addressListener);
-        mSaveCc.setOnCheckedChangeListener(mExpectation.saveCcListener);
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, int, int, boolean)}.
-     */
-    void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        mExpectation.ccNumberWatcher.assertAutoFilled();
-        mExpectation.ccExpirationListener.assertAutoFilled();
-        mExpectation.addressListener.assertAutoFilled();
-        mExpectation.saveCcListener.assertAutoFilled();
-    }
-
-    /**
-     * Visits the {@code ccNumber} in the UiThread.
-     */
-    void onCcNumber(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mCcNumber));
-    }
-
-    /**
-     * Visits the {@code ccExpirationDate} in the UiThread.
-     */
-    void onCcExpiration(Visitor<Spinner> v) {
-        syncRunOnUiThread(() -> v.visit(mCcExpiration));
-    }
-
-    /**
-     * Visits the {@code ccExpirationDate} adapter in the UiThread.
-     */
-    void onCcExpirationAdapter(Visitor<ArrayAdapter<CharSequence>> v) {
-        syncRunOnUiThread(() -> v.visit(mCcExpirationAdapter));
-    }
-
-    /**
-     * Visits the {@code address} in the UiThread.
-     */
-    void onAddress(Visitor<RadioGroup> v) {
-        syncRunOnUiThread(() -> v.visit(mAddress));
-    }
-
-    /**
-     * Visits the {@code homeAddress} in the UiThread.
-     */
-    void onHomeAddress(Visitor<RadioButton> v) {
-        syncRunOnUiThread(() -> v.visit(mHomeAddress));
-    }
-
-    /**
-     * Visits the {@code saveCC} in the UiThread.
-     */
-    void onSaveCc(Visitor<CheckBox> v) {
-        syncRunOnUiThread(() -> v.visit(mSaveCc));
-    }
-
-    /**
-     * Taps the buy button in the UI thread.
-     */
-    void tapBuy() throws Exception {
-        mBuyLatch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> mBuyButton.performClick());
-        boolean called = mBuyLatch.await(BUY_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) waiting for buy action", BUY_TIMEOUT_MS)
-                .that(called).isTrue();
-    }
-
-    EditText getCcNumber() {
-        return mCcNumber;
-    }
-
-    Spinner getCcExpiration() {
-        return mCcExpiration;
-    }
-
-    ArrayAdapter<CharSequence> getCcExpirationAdapter() {
-        return mCcExpirationAdapter;
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final OneTimeTextWatcher ccNumberWatcher;
-        private final OneTimeSpinnerListener ccExpirationListener;
-        private final OneTimeRadioGroupListener addressListener;
-        private final OneTimeCompoundButtonListener saveCcListener;
-
-        private FillExpectation(String ccNumber, int ccExpirationIndex, int addressId,
-                boolean saveCc) {
-            this.ccNumberWatcher = new OneTimeTextWatcher("ccNumber", mCcNumber, ccNumber);
-            this.ccExpirationListener =
-                    new OneTimeSpinnerListener("ccExpiration", mCcExpiration, ccExpirationIndex);
-            addressListener = new OneTimeRadioGroupListener("address", mAddress, addressId);
-            saveCcListener = new OneTimeCompoundButtonListener("saveCc", mSaveCc, saveCc);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
deleted file mode 100644
index 3d995b9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CheckoutActivity.ID_ADDRESS;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_EXPIRATION;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_NUMBER;
-import static android.autofillservice.cts.CheckoutActivity.ID_HOME_ADDRESS;
-import static android.autofillservice.cts.CheckoutActivity.ID_SAVE_CC;
-import static android.autofillservice.cts.CheckoutActivity.ID_WORK_ADDRESS;
-import static android.autofillservice.cts.CheckoutActivity.INDEX_ADDRESS_WORK;
-import static android.autofillservice.cts.CheckoutActivity.INDEX_CC_EXPIRATION_NEVER;
-import static android.autofillservice.cts.CheckoutActivity.INDEX_CC_EXPIRATION_TODAY;
-import static android.autofillservice.cts.CheckoutActivity.INDEX_CC_EXPIRATION_TOMORROW;
-import static android.autofillservice.cts.Helper.assertListValue;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertToggleIsSanitized;
-import static android.autofillservice.cts.Helper.assertToggleValue;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
-import static android.view.View.AUTOFILL_TYPE_LIST;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.FillContext;
-import android.service.autofill.ImageTransformation;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.view.autofill.AutofillId;
-import android.widget.ArrayAdapter;
-import android.widget.RemoteViews;
-import android.widget.Spinner;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.regex.Pattern;
-
-/**
- * Test case for an activity containing non-TextField views.
- */
-public class CheckoutActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<CheckoutActivity> {
-
-    private CheckoutActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<CheckoutActivity> getActivityRule() {
-        return new AutofillActivityTestRule<CheckoutActivity>(CheckoutActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testAutofill() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setPresentation(createPresentation("ACME CC"))
-                .setField(ID_CC_NUMBER, "4815162342")
-                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
-                .setField(ID_ADDRESS, 1)
-                .setField(ID_SAVE_CC, true)
-                .build());
-        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
-                true);
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert properties of Spinner field.
-        final ViewNode ccExpirationNode =
-                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
-        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
-        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
-        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
-        assertWithMessage("ccExpirationNode.getAutoFillOptions()").that(options).isNotNull();
-        assertWithMessage("Wrong auto-fill options for spinner").that(options).asList()
-                .containsExactly((Object [])
-                        getContext().getResources().getStringArray(R.array.cc_expiration_values))
-                .inOrder();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("ACME CC");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofill() is enough")
-    public void testAutofillDynamicAdapter() throws Exception {
-        // Set activity.
-        mActivity.onCcExpiration((v) -> v.setAdapter(new ArrayAdapter<String>(getContext(),
-                android.R.layout.simple_spinner_item,
-                Arrays.asList("YESTERDAY", "TODAY", "TOMORROW", "NEVER"))));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setPresentation(createPresentation("ACME CC"))
-                .setField(ID_CC_NUMBER, "4815162342")
-                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
-                .setField(ID_ADDRESS, 1)
-                .setField(ID_SAVE_CC, true)
-                .build());
-        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
-                true);
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert properties of Spinner field.
-        final ViewNode ccExpirationNode =
-                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
-        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
-        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
-        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
-        assertWithMessage("ccExpirationNode.getAutoFillOptions()").that(options).isNull();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("ACME CC");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    // TODO: this should be a pure unit test exercising onProvideAutofillStructure(),
-    // but that would require creating a custom ViewStructure.
-    @Test
-    @AppModeFull(reason = "Unit test")
-    public void testGetAutofillOptionsSorted() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set activity.
-        mActivity.onCcExpirationAdapter((adapter) -> adapter.sort((a, b) -> {
-            return ((String) a).compareTo((String) b);
-        }));
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setPresentation(createPresentation("ACME CC"))
-                .setField(ID_CC_NUMBER, "4815162342")
-                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
-                .setField(ID_ADDRESS, 1)
-                .setField(ID_SAVE_CC, true)
-                .build());
-        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
-                true);
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert properties of Spinner field.
-        final ViewNode ccExpirationNode =
-                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
-        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
-        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
-        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
-        assertWithMessage("Wrong auto-fill options for spinner").that(options).asList()
-                .containsExactly("never", "today", "tomorrow", "yesterday").inOrder();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("ACME CC");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testSanitization() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD,
-                        ID_CC_NUMBER, ID_CC_EXPIRATION, ID_ADDRESS, ID_SAVE_CC)
-                .build());
-
-        // Dynamically change view contents
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
-        mActivity.onHomeAddress((v) -> v.setChecked(true));
-        mActivity.onSaveCc((v) -> v.setChecked(true));
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-
-        // Assert sanitization on fill request: everything should be sanitized!
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        assertTextIsSanitized(fillRequest.structure, ID_CC_NUMBER);
-        assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
-        assertToggleIsSanitized(fillRequest.structure, ID_HOME_ADDRESS);
-        assertToggleIsSanitized(fillRequest.structure, ID_SAVE_CC);
-
-        // Trigger save.
-        mActivity.onCcNumber((v) -> v.setText("4815162342"));
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
-        mActivity.onAddress((v) -> v.check(R.id.work_address));
-        mActivity.onSaveCc((v) -> v.setChecked(false));
-        mActivity.tapBuy();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_CREDIT_CARD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Assert sanitization on save: everything should be available!
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CC_NUMBER), "4815162342");
-        assertListValue(findNodeByResourceId(saveRequest.structure, ID_CC_EXPIRATION),
-                INDEX_CC_EXPIRATION_TODAY);
-        assertListValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS),
-                INDEX_ADDRESS_WORK);
-        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_HOME_ADDRESS), false);
-        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_WORK_ADDRESS), true);
-        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_SAVE_CC), false);
-    }
-
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testCustomizedSaveUi() throws Exception {
-        customizedSaveUi(false);
-    }
-
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testCustomizedSaveUiWithContentDescription() throws Exception {
-        customizedSaveUi(true);
-    }
-
-    /**
-     * Tests that a spinner can be used on custom save descriptions.
-     */
-    private void customizedSaveUi(boolean withContentDescription) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final String packageName = getContext().getPackageName();
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD, ID_CC_NUMBER, ID_CC_EXPIRATION)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final RemoteViews presentation = new RemoteViews(packageName,
-                            R.layout.two_horizontal_text_fields);
-                    final FillContext context = contexts.get(0);
-                    final AutofillId ccNumberId = findAutofillIdByResourceId(context,
-                            ID_CC_NUMBER);
-                    final AutofillId ccExpirationId = findAutofillIdByResourceId(context,
-                            ID_CC_EXPIRATION);
-                    final CharSequenceTransformation trans1 = new CharSequenceTransformation
-                            .Builder(ccNumberId, Pattern.compile("(.*)"), "$1")
-                            .build();
-                    final CharSequenceTransformation trans2 = new CharSequenceTransformation
-                            .Builder(ccExpirationId, Pattern.compile("(.*)"), "$1")
-                            .build();
-                    final ImageTransformation trans3 = (withContentDescription
-                            ? new ImageTransformation.Builder(ccNumberId,
-                                    Pattern.compile("(.*)"), R.drawable.android,
-                                    "One image is worth thousand words")
-                            : new ImageTransformation.Builder(ccNumberId,
-                                    Pattern.compile("(.*)"), R.drawable.android))
-                            .build();
-
-                    final CustomDescription customDescription =
-                            new CustomDescription.Builder(presentation)
-                            .addChild(R.id.first, trans1)
-                            .addChild(R.id.second, trans2)
-                            .addChild(R.id.img, trans3)
-                            .build();
-                    builder.setCustomDescription(customDescription);
-                })
-                .build());
-
-        // Dynamically change view contents
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onCcNumber((v) -> v.setText("4815162342"));
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
-        mActivity.tapBuy();
-
-        // First make sure the UI is shown...
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
-
-        // Then make sure it does have the custom views on it...
-        final UiObject2 staticText = saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT));
-        assertThat(staticText).isNotNull();
-        assertThat(staticText.getText()).isEqualTo("YO:");
-
-        final UiObject2 number = saveUi.findObject(By.res(packageName, "first"));
-        assertThat(number).isNotNull();
-        assertThat(number.getText()).isEqualTo("4815162342");
-
-        final UiObject2 expiration = saveUi.findObject(By.res(packageName, "second"));
-        assertThat(expiration).isNotNull();
-        assertThat(expiration.getText()).isEqualTo("today");
-
-        final UiObject2 image = saveUi.findObject(By.res(packageName, "img"));
-        assertThat(image).isNotNull();
-        final String contentDescription = image.getContentDescription();
-        if (withContentDescription) {
-            assertThat(contentDescription).isEqualTo("One image is worth thousand words");
-        } else {
-            assertThat(contentDescription).isNull();
-        }
-    }
-
-    /**
-     * Tests that a custom save description is ignored when the selected spinner element is not
-     * available in the autofill options.
-     */
-    @Test
-    public void testCustomizedSaveUiWhenListResolutionFails() throws Exception {
-        // Set service.
-        enableService();
-
-        // Change spinner to return just one item so the transformation throws an exception when
-        // fetching it.
-        mActivity.getCcExpirationAdapter().setAutofillOptions("D'OH!");
-
-        // Set expectations.
-        final String packageName = getContext().getPackageName();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD, ID_CC_NUMBER, ID_CC_EXPIRATION)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId ccNumberId = findAutofillIdByResourceId(context,
-                            ID_CC_NUMBER);
-                    final AutofillId ccExpirationId = findAutofillIdByResourceId(context,
-                            ID_CC_EXPIRATION);
-                    final RemoteViews presentation = new RemoteViews(packageName,
-                            R.layout.two_horizontal_text_fields);
-                    final CharSequenceTransformation trans1 = new CharSequenceTransformation
-                            .Builder(ccNumberId, Pattern.compile("(.*)"), "$1")
-                            .build();
-                    final CharSequenceTransformation trans2 = new CharSequenceTransformation
-                            .Builder(ccExpirationId, Pattern.compile("(.*)"), "$1")
-                            .build();
-                    final CustomDescription customDescription =
-                            new CustomDescription.Builder(presentation)
-                            .addChild(R.id.first, trans1)
-                            .addChild(R.id.second, trans2)
-                            .build();
-                    builder.setCustomDescription(customDescription);
-                })
-                .build());
-
-        // Dynamically change view contents
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onCcNumber((v) -> v.setText("4815162342"));
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
-        mActivity.tapBuy();
-
-        // First make sure the UI is shown...
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
-
-        // Then make sure it does not have the custom views on it...
-        assertThat(saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT))).isNull();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CompositeUserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/CompositeUserDataTest.java
deleted file mode 100644
index a00e0d3..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CompositeUserDataTest.java
+++ /dev/null
@@ -1,225 +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.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CompositeUserData;
-import android.service.autofill.UserData;
-import android.util.ArrayMap;
-
-import com.google.common.base.Strings;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class CompositeUserDataTest {
-
-    private final String mShortValue = Strings.repeat("k", UserData.getMinValueLength() - 1);
-    private final String mLongValue = "LONG VALUE, Y U NO SHORTER"
-            + Strings.repeat("?", UserData.getMaxValueLength());
-    private final String mId = "4815162342";
-    private final String mId2 = "4815162343";
-    private final String mCategoryId = "id1";
-    private final String mCategoryId2 = "id2";
-    private final String mCategoryId3 = "id3";
-    private final String mValue = mShortValue + "-1";
-    private final String mValue2 = mShortValue + "-2";
-    private final String mValue3 = mShortValue + "-3";
-    private final String mValue4 = mShortValue + "-4";
-    private final String mValue5 = mShortValue + "-5";
-    private final String mAlgo = "algo";
-    private final String mAlgo2 = "algo2";
-    private final String mAlgo3 = "algo3";
-    private final String mAlgo4 = "algo4";
-
-    private final UserData mEmptyGenericUserData = new UserData.Builder(mId, mValue, mCategoryId)
-            .build();
-    private final UserData mLoadedGenericUserData = new UserData.Builder(mId, mValue, mCategoryId)
-            .add(mValue2, mCategoryId2)
-            .setFieldClassificationAlgorithm(mAlgo, createBundle(false))
-            .setFieldClassificationAlgorithmForCategory(mCategoryId2, mAlgo2, createBundle(false))
-            .build();
-    private final UserData mEmptyPackageUserData = new UserData.Builder(mId2, mValue3, mCategoryId3)
-            .build();
-    private final UserData mLoadedPackageUserData = new UserData
-            .Builder(mId2, mValue3, mCategoryId3)
-            .add(mValue4, mCategoryId2)
-            .setFieldClassificationAlgorithm(mAlgo3, createBundle(true))
-            .setFieldClassificationAlgorithmForCategory(mCategoryId2, mAlgo4, createBundle(true))
-            .build();
-
-
-    @Test
-    public void testMergeInvalid_bothNull() {
-        assertThrows(NullPointerException.class, () -> new CompositeUserData(null, null));
-    }
-
-    @Test
-    public void testMergeInvalid_nullPackageUserData() {
-        assertThrows(NullPointerException.class,
-                () -> new CompositeUserData(mEmptyGenericUserData, null));
-    }
-
-    @Test
-    public void testMerge_nullGenericUserData() {
-        final CompositeUserData userData = new CompositeUserData(null, mEmptyPackageUserData);
-
-        final String[] categoryIds = userData.getCategoryIds();
-        assertThat(categoryIds.length).isEqualTo(1);
-        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
-
-        final String[] values = userData.getValues();
-        assertThat(values.length).isEqualTo(1);
-        assertThat(values[0]).isEqualTo(mValue3);
-
-        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
-        assertThat(userData.getDefaultFieldClassificationArgs()).isNull();
-    }
-
-    @Test
-    public void testMerge_bothEmpty() {
-        final CompositeUserData userData = new CompositeUserData(mEmptyGenericUserData,
-                mEmptyPackageUserData);
-
-        final String[] categoryIds = userData.getCategoryIds();
-        assertThat(categoryIds.length).isEqualTo(2);
-        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
-        assertThat(categoryIds[1]).isEqualTo(mCategoryId);
-
-        final String[] values = userData.getValues();
-        assertThat(values.length).isEqualTo(2);
-        assertThat(values[0]).isEqualTo(mValue3);
-        assertThat(values[1]).isEqualTo(mValue);
-
-        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
-        assertThat(userData.getDefaultFieldClassificationArgs()).isNull();
-    }
-
-    @Test
-    public void testMerge_emptyGenericUserData() {
-        final CompositeUserData userData = new CompositeUserData(mEmptyGenericUserData,
-                mLoadedPackageUserData);
-
-        final String[] categoryIds = userData.getCategoryIds();
-        assertThat(categoryIds.length).isEqualTo(3);
-        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
-        assertThat(categoryIds[1]).isEqualTo(mCategoryId2);
-        assertThat(categoryIds[2]).isEqualTo(mCategoryId);
-
-        final String[] values = userData.getValues();
-        assertThat(values.length).isEqualTo(3);
-        assertThat(values[0]).isEqualTo(mValue3);
-        assertThat(values[1]).isEqualTo(mValue4);
-        assertThat(values[2]).isEqualTo(mValue);
-
-        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo3);
-
-        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
-        assertThat(defaultArgs).isNotNull();
-        assertThat(defaultArgs.getBoolean("isPackage")).isTrue();
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
-                .isEqualTo(mAlgo4);
-
-        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
-        assertThat(args.size()).isEqualTo(1);
-        assertThat(args.containsKey(mCategoryId2)).isTrue();
-        assertThat(args.get(mCategoryId2)).isNotNull();
-        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isTrue();
-    }
-
-    @Test
-    public void testMerge_emptyPackageUserData() {
-        final CompositeUserData userData = new CompositeUserData(mLoadedGenericUserData,
-                mEmptyPackageUserData);
-
-        final String[] categoryIds = userData.getCategoryIds();
-        assertThat(categoryIds.length).isEqualTo(3);
-        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
-        assertThat(categoryIds[1]).isEqualTo(mCategoryId);
-        assertThat(categoryIds[2]).isEqualTo(mCategoryId2);
-
-        final String[] values = userData.getValues();
-        assertThat(values.length).isEqualTo(3);
-        assertThat(values[0]).isEqualTo(mValue3);
-        assertThat(values[1]).isEqualTo(mValue);
-        assertThat(values[2]).isEqualTo(mValue2);
-
-        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo);
-
-        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
-        assertThat(defaultArgs).isNotNull();
-        assertThat(defaultArgs.getBoolean("isPackage")).isFalse();
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
-                .isEqualTo(mAlgo2);
-
-        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
-        assertThat(args.size()).isEqualTo(1);
-        assertThat(args.containsKey(mCategoryId2)).isTrue();
-        assertThat(args.get(mCategoryId2)).isNotNull();
-        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isFalse();
-    }
-
-
-    @Test
-    public void testMerge_bothHaveData() {
-        final CompositeUserData userData = new CompositeUserData(mLoadedGenericUserData,
-                mLoadedPackageUserData);
-
-        final String[] categoryIds = userData.getCategoryIds();
-        assertThat(categoryIds.length).isEqualTo(3);
-        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
-        assertThat(categoryIds[1]).isEqualTo(mCategoryId2);
-        assertThat(categoryIds[2]).isEqualTo(mCategoryId);
-
-        final String[] values = userData.getValues();
-        assertThat(values.length).isEqualTo(3);
-        assertThat(values[0]).isEqualTo(mValue3);
-        assertThat(values[1]).isEqualTo(mValue4);
-        assertThat(values[2]).isEqualTo(mValue);
-
-        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo3);
-        assertThat(userData.getDefaultFieldClassificationArgs()).isNotNull();
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
-                .isEqualTo(mAlgo4);
-
-        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
-        assertThat(defaultArgs).isNotNull();
-        assertThat(defaultArgs.getBoolean("isPackage")).isTrue();
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
-                .isEqualTo(mAlgo4);
-
-        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
-        assertThat(args.size()).isEqualTo(1);
-        assertThat(args.containsKey(mCategoryId2)).isTrue();
-        assertThat(args.get(mCategoryId2)).isNotNull();
-        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isTrue();
-    }
-
-    private Bundle createBundle(Boolean isPackageBundle) {
-        final Bundle bundle = new Bundle();
-        bundle.putBoolean("isPackage", isPackageBundle);
-        return bundle;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java
deleted file mode 100644
index ad89227..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java
+++ /dev/null
@@ -1,166 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.AbstractDatePickerActivity.ID_DATE_PICKER;
-import static android.autofillservice.cts.AbstractDatePickerActivity.ID_OUTPUT;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.icu.text.SimpleDateFormat;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.DateTransformation;
-import android.service.autofill.DateValueSanitizer;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import org.junit.Test;
-
-import java.util.Calendar;
-
-@AppModeFull(reason = "Service-specific test")
-public class CustomDescriptionDateTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<DatePickerSpinnerActivity> {
-
-    private DatePickerSpinnerActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<DatePickerSpinnerActivity> getActivityRule() {
-        return new AutofillActivityTestRule<DatePickerSpinnerActivity>(
-                DatePickerSpinnerActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testCustomSave() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0),
-                            ID_DATE_PICKER);
-                    builder.setCustomDescription(new CustomDescription
-                            .Builder(newTemplate(R.layout.two_horizontal_text_fields))
-                            .addChild(R.id.first,
-                                    new DateTransformation(id, new SimpleDateFormat("MM/yyyy")))
-                            .addChild(R.id.second,
-                                    new DateTransformation(id, new SimpleDateFormat("MM-yy")))
-                            .build());
-                })
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onOutput((v) -> v.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Autofill it.
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save.
-        mActivity.setDate(2010, Calendar.DECEMBER, 12);
-        mActivity.tapOk();
-
-        // First, make sure the UI is shown...
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Then, make sure it does have the custom view on it...
-        final UiObject2 staticText = saveUi.findObject(By.res(mPackageName, "static_text"));
-        assertThat(staticText).isNotNull();
-        assertThat(staticText.getText()).isEqualTo("YO:");
-
-        // Finally, assert the custom lines are shown
-        mUiBot.assertChild(saveUi, "first", (o) -> assertThat(o.getText()).isEqualTo("12/2010"));
-        mUiBot.assertChild(saveUi, "second", (o) -> assertThat(o.getText()).isEqualTo("12-10"));
-    }
-
-    @Test
-    public void testSaveSameValue_usingSanitization() throws Exception {
-        sanitizationTest(true);
-    }
-
-    @Test
-    public void testSaveSameValue_withoutSanitization() throws Exception {
-        sanitizationTest(false);
-    }
-
-    private void sanitizationTest(boolean withSanitization) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Calendar cal = Calendar.getInstance();
-        cal.clear();
-        cal.set(Calendar.YEAR, 2012);
-        cal.set(Calendar.MONTH, Calendar.DECEMBER);
-
-        // Set expectations.
-
-        // NOTE: ID_OUTPUT is used to trigger autofill, but it's value will be automatically
-        // changed, hence we need to set the expected value as the formated one. Ideally
-        // we shouldn't worry about that, but that would require creating a new activitiy with
-        // a custom edit text that uses date autofill values...
-        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("The end of the world"))
-                        .setField(ID_OUTPUT, "2012/11/25")
-                        .setField(ID_DATE_PICKER, cal.getTimeInMillis())
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER);
-
-        if (withSanitization) {
-            response.setSaveInfoVisitor((contexts, builder) -> {
-                final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_DATE_PICKER);
-                builder.addSanitizer(new DateValueSanitizer(new SimpleDateFormat("MM/yyyy")), id);
-            });
-        }
-        sReplier.addResponse(response.build());
-
-        // Trigger autofill.
-        mActivity.onOutput((v) -> v.requestFocus());
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The end of the world");
-
-        // Manually set same values as dataset.
-        mActivity.onOutput((v) -> v.setText("whatever"));
-        mActivity.setDate(2012, Calendar.DECEMBER, 25);
-        mActivity.tapOk();
-
-        // Verify save behavior.
-        if (withSanitization) {
-            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-        } else {
-            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        }
-    }
-
-    private RemoteViews newTemplate(int resourceId) {
-        return new RemoteViews(getContext().getPackageName(), resourceId);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionHelper.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionHelper.java
deleted file mode 100644
index 5fc33e7..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionHelper.java
+++ /dev/null
@@ -1,49 +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.autofillservice.cts;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import android.service.autofill.CustomDescription;
-import android.widget.RemoteViews;
-
-public final class CustomDescriptionHelper {
-
-    public static final String ID_SHOW = "show";
-    public static final String ID_HIDE = "hide";
-    public static final String ID_USERNAME_PLAIN = "username_plain";
-    public static final String ID_USERNAME_MASKED = "username_masked";
-    public static final String ID_PASSWORD_PLAIN = "password_plain";
-    public static final String ID_PASSWORD_MASKED = "password_masked";
-
-    private static final String sPackageName =
-            getInstrumentation().getTargetContext().getPackageName();
-
-
-    public static CustomDescription.Builder newCustomDescriptionWithUsernameAndPassword() {
-        return new CustomDescription.Builder(new RemoteViews(sPackageName,
-                R.layout.custom_description_with_username_and_password));
-    }
-
-    public static CustomDescription.Builder newCustomDescriptionWithHiddenFields() {
-        return new CustomDescription.Builder(new RemoteViews(sPackageName,
-                R.layout.custom_description_with_hidden_fields));
-    }
-
-    private CustomDescriptionHelper() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
deleted file mode 100644
index 0c6792d..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
+++ /dev/null
@@ -1,642 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.BatchUpdates;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.FillContext;
-import android.service.autofill.ImageTransformation;
-import android.service.autofill.RegexValidator;
-import android.service.autofill.TextValueSanitizer;
-import android.service.autofill.Validator;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.junit.Test;
-
-import java.util.function.BiFunction;
-import java.util.regex.Pattern;
-
-@AppModeFull(reason = "Service-specific test")
-public class CustomDescriptionTest extends AbstractLoginActivityTestCase {
-
-    /**
-     * Base test
-     *
-     * @param descriptionBuilder method to build a custom description
-     * @param uiVerifier         Ran when the custom description is shown
-     */
-    private void testCustomDescription(
-            @NonNull BiFunction<AutofillId, AutofillId, CustomDescription> descriptionBuilder,
-            @Nullable Runnable uiVerifier) throws Exception {
-        enableService();
-
-        // Set response with custom description
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME, ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.setCustomDescription(descriptionBuilder.apply(usernameId, passwordId));
-                })
-                .build());
-
-        // Trigger autofill with custom description
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText("usernm"));
-        mActivity.onPassword((v) -> v.setText("passwd"));
-        mActivity.tapLogin();
-
-        if (uiVerifier != null) {
-            uiVerifier.run();
-        }
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-        sReplier.getNextSaveRequest();
-    }
-
-    @Test
-    public void testSanitizationBeforeBatchUpdates() throws Exception {
-        enableService();
-
-        // Set response with custom description
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final RemoteViews presentation =
-                            newTemplate(R.layout.two_horizontal_text_fields);
-
-                    final AutofillId usernameId =
-                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
-
-                    // Validator for sanitization
-                    final Validator validCondition =
-                            new RegexValidator(usernameId, Pattern.compile("user"));
-
-                    final RemoteViews update = newTemplate(-666); // layout id not really used
-                    update.setTextViewText(R.id.first, "batch updated");
-
-                    final CustomDescription customDescription = new CustomDescription
-                            .Builder(presentation)
-                            .batchUpdate(validCondition,
-                                    new BatchUpdates.Builder().updateTemplate(update).build())
-                            .build();
-                    builder
-                        .addSanitizer(new TextValueSanitizer(Pattern.compile("USERNAME"), "user"),
-                                usernameId)
-                        .setCustomDescription(customDescription);
-
-                })
-                .build());
-
-        // Trigger autofill with custom description
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
-        mActivity.tapLogin();
-
-        assertSaveUiIsShownWithTwoLines("batch updated");
-    }
-
-    @Test
-    public void testSanitizationBeforeTransformations() throws Exception {
-        enableService();
-
-        // Set response with custom description
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final RemoteViews presentation =
-                            newTemplate(R.layout.two_horizontal_text_fields);
-
-                    final AutofillId usernameId =
-                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
-
-                    // Transformation
-                    final CharSequenceTransformation trans = new CharSequenceTransformation
-                            .Builder(usernameId, Pattern.compile("user"), "transformed")
-                            .build();
-
-                    final CustomDescription customDescription = new CustomDescription
-                            .Builder(presentation)
-                            .addChild(R.id.first, trans)
-                            .build();
-                    builder
-                        .addSanitizer(new TextValueSanitizer(Pattern.compile("USERNAME"), "user"),
-                                usernameId)
-                        .setCustomDescription(customDescription);
-
-                })
-                .build());
-
-        // Trigger autofill with custom description
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
-        mActivity.tapLogin();
-
-        assertSaveUiIsShownWithTwoLines("transformed");
-    }
-
-    @Test
-    public void validTransformation() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans1 = new CharSequenceTransformation
-                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
-                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
-                    .build();
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans2 = new ImageTransformation
-                    .Builder(usernameId, Pattern.compile(".*"),
-                    R.drawable.android).build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans1)
-                    .addChild(R.id.img, trans2)
-                    .build();
-        }, () -> assertSaveUiIsShownWithTwoLines("usernm..wd"));
-    }
-
-    @Test
-    public void validTransformationWithOneTemplateUpdate() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans1 = new CharSequenceTransformation
-                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
-                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
-                    .build();
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans2 = new ImageTransformation
-                    .Builder(usernameId, Pattern.compile(".*"),
-                    R.drawable.android).build();
-            RemoteViews update = newTemplate(0); // layout id not really used
-            update.setViewVisibility(R.id.second, View.GONE);
-            Validator condition = new RegexValidator(usernameId, Pattern.compile(".*"));
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans1)
-                    .addChild(R.id.img, trans2)
-                    .batchUpdate(condition,
-                            new BatchUpdates.Builder().updateTemplate(update).build())
-                    .build();
-        }, () -> assertSaveUiIsShownWithJustOneLine("usernm..wd"));
-    }
-
-    @Test
-    public void validTransformationWithMultipleTemplateUpdates() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans1 = new CharSequenceTransformation.Builder(usernameId,
-                    Pattern.compile("(.*)"), "$1")
-                            .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
-                            .build();
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans2 = new ImageTransformation.Builder(usernameId,
-                    Pattern.compile(".*"), R.drawable.android)
-                    .build();
-
-            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
-            Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
-
-            // Line 1 updates
-            RemoteViews update1 = newTemplate(666); // layout id not really used
-            update1.setContentDescription(R.id.first, "First am I"); // valid
-            RemoteViews update2 = newTemplate(0); // layout id not really used
-            update2.setViewVisibility(R.id.first, View.GONE); // invalid
-
-            // Line 2 updates
-            RemoteViews update3 = newTemplate(-666); // layout id not really used
-            update3.setTextViewText(R.id.second, "First of his second name"); // valid
-            RemoteViews update4 = newTemplate(0); // layout id not really used
-            update4.setTextViewText(R.id.second, "SECOND of his second name"); // invalid
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans1)
-                    .addChild(R.id.img, trans2)
-                    .batchUpdate(validCondition,
-                            new BatchUpdates.Builder().updateTemplate(update1).build())
-                    .batchUpdate(invalidCondition,
-                            new BatchUpdates.Builder().updateTemplate(update2).build())
-                    .batchUpdate(validCondition,
-                            new BatchUpdates.Builder().updateTemplate(update3).build())
-                    .batchUpdate(invalidCondition,
-                            new BatchUpdates.Builder().updateTemplate(update4).build())
-                    .build();
-        }, () -> assertSaveUiWithLinesIsShown(
-                (line1) -> assertWithMessage("Wrong content description for line1")
-                        .that(line1.getContentDescription()).isEqualTo("First am I"),
-                (line2) -> assertWithMessage("Wrong text for line2").that(line2.getText())
-                        .isEqualTo("First of his second name"),
-                null));
-    }
-
-    @Test
-    public void testMultipleBatchUpdates_noConditionPass() throws Exception {
-        multipleBatchUpdatesTest(BatchUpdatesConditionType.NONE_PASS);
-    }
-
-    @Test
-    public void testMultipleBatchUpdates_secondConditionPass() throws Exception {
-        multipleBatchUpdatesTest(BatchUpdatesConditionType.SECOND_PASS);
-    }
-
-    @Test
-    public void testMultipleBatchUpdates_thirdConditionPass() throws Exception {
-        multipleBatchUpdatesTest(BatchUpdatesConditionType.THIRD_PASS);
-    }
-
-    @Test
-    public void testMultipleBatchUpdates_allConditionsPass() throws Exception {
-        multipleBatchUpdatesTest(BatchUpdatesConditionType.ALL_PASS);
-    }
-
-    private enum BatchUpdatesConditionType {
-        NONE_PASS,
-        SECOND_PASS,
-        THIRD_PASS,
-        ALL_PASS
-    }
-
-    /**
-     * Tests a custom description that has 3 transformations, one applied directly and the other
-     * 2 in batch updates.
-     *
-     * @param conditionsType defines which batch updates conditions will pass.
-     */
-    private void multipleBatchUpdatesTest(BatchUpdatesConditionType conditionsType)
-            throws Exception {
-
-        final boolean line2Pass = conditionsType == BatchUpdatesConditionType.SECOND_PASS
-                || conditionsType == BatchUpdatesConditionType.ALL_PASS;
-        final boolean line3Pass = conditionsType == BatchUpdatesConditionType.THIRD_PASS
-                || conditionsType == BatchUpdatesConditionType.ALL_PASS;
-
-        final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
-                .that(line1.getText()).isEqualTo("L1-u");
-
-        final Visitor<UiObject2> line2Visitor;
-        if (line2Pass) {
-            line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
-                    .that(line2.getText()).isEqualTo("L2-u");
-        } else {
-            line2Visitor = null;
-        }
-
-        final Visitor<UiObject2> line3Visitor;
-        if (line3Pass) {
-            line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
-                    .that(line3.getText()).isEqualTo("L3-p");
-        } else {
-            line3Visitor = null;
-        }
-
-        testCustomDescription((usernameId, passwordId) -> {
-            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
-            Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
-            Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
-
-            final RemoteViews presentation =
-                    newTemplate(R.layout.three_horizontal_text_fields_last_two_invisible);
-
-            final CharSequenceTransformation line1Transformation =
-                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
-                        .build();
-
-            final CharSequenceTransformation line2Transformation =
-                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
-                        .build();
-            final RemoteViews line2Updates = newTemplate(666); // layout id not really used
-            line2Updates.setViewVisibility(R.id.second, View.VISIBLE);
-
-            final CharSequenceTransformation line3Transformation =
-                    new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
-                        .build();
-            final RemoteViews line3Updates = newTemplate(666); // layout id not really used
-            line3Updates.setViewVisibility(R.id.third, View.VISIBLE);
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, line1Transformation)
-                    .batchUpdate(line2Pass ? validCondition : invalidCondition,
-                            new BatchUpdates.Builder()
-                            .transformChild(R.id.second, line2Transformation)
-                            .updateTemplate(line2Updates)
-                            .build())
-                    .batchUpdate(line3Pass ? validCondition : invalidCondition,
-                            new BatchUpdates.Builder()
-                            .transformChild(R.id.third, line3Transformation)
-                            .updateTemplate(line3Updates)
-                            .build())
-                    .build();
-        }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
-    }
-
-    @Test
-    public void testBatchUpdatesApplyUpdateFirstThenTransformations() throws Exception {
-
-        final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
-                .that(line1.getText()).isEqualTo("L1-u");
-        final Visitor<UiObject2> line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
-                .that(line2.getText()).isEqualTo("L2-u");
-        final Visitor<UiObject2> line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
-                .that(line3.getText()).isEqualTo("L3-p");
-
-        testCustomDescription((usernameId, passwordId) -> {
-            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
-            Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
-
-            final RemoteViews presentation =
-                    newTemplate(R.layout.two_horizontal_text_fields);
-
-            final CharSequenceTransformation line1Transformation =
-                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
-                        .build();
-
-            final CharSequenceTransformation line2Transformation =
-                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
-                        .build();
-
-            final CharSequenceTransformation line3Transformation =
-                    new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
-                        .build();
-            final RemoteViews line3Presentation = newTemplate(R.layout.third_line_only);
-            final RemoteViews line3Updates = newTemplate(666); // layout id not really used
-            line3Updates.addView(R.id.parent, line3Presentation);
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, line1Transformation)
-                    .batchUpdate(validCondition,
-                            new BatchUpdates.Builder()
-                            .transformChild(R.id.second, line2Transformation)
-                            .build())
-                    .batchUpdate(validCondition,
-                            new BatchUpdates.Builder()
-                            .updateTemplate(line3Updates)
-                            .transformChild(R.id.third, line3Transformation)
-                            .build())
-                    .build();
-        }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
-    }
-
-    @Test
-    public void badImageTransformation() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans = new ImageTransformation.Builder(usernameId,
-                    Pattern.compile(".*"), 1).build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.img, trans)
-                    .build();
-        }, () -> assertSaveUiWithCustomDescriptionIsShown());
-    }
-
-    @Test
-    public void unusedImageTransformation() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans = new ImageTransformation
-                    .Builder(usernameId, Pattern.compile("invalid"), R.drawable.android)
-                    .build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.img, trans)
-                    .build();
-        }, () -> assertSaveUiWithCustomDescriptionIsShown());
-    }
-
-    @Test
-    public void applyImageTransformationToTextView() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans = new ImageTransformation
-                    .Builder(usernameId, Pattern.compile(".*"), R.drawable.android)
-                    .build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans)
-                    .build();
-        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
-    }
-
-    @Test
-    public void failFirstFailAll() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans = new CharSequenceTransformation
-                    .Builder(usernameId, Pattern.compile("(.*)"), "$42")
-                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
-                    .build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans)
-                    .build();
-        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
-    }
-
-    @Test
-    public void failSecondFailAll() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans = new CharSequenceTransformation
-                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
-                    .addField(passwordId, Pattern.compile(".*(..)"), "..$42")
-                    .build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans)
-                    .build();
-        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
-    }
-
-    @Test
-    public void applyCharSequenceTransformationToImageView() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans = new CharSequenceTransformation
-                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
-                    .build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.img, trans)
-                    .build();
-        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
-    }
-
-    private void multipleTransformationsForSameFieldTest(boolean matchFirst) throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    // Set response with custom description
-                    final AutofillId usernameId =
-                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
-                    final CharSequenceTransformation firstTrans = new CharSequenceTransformation
-                            .Builder(usernameId, Pattern.compile("(marco)"), "polo")
-                            .build();
-                    final CharSequenceTransformation secondTrans = new CharSequenceTransformation
-                            .Builder(usernameId, Pattern.compile("(MARCO)"), "POLO")
-                            .build();
-                    final RemoteViews presentation =
-                            newTemplate(R.layout.two_horizontal_text_fields);
-                    final CustomDescription customDescription =
-                            new CustomDescription.Builder(presentation)
-                            .addChild(R.id.first, firstTrans)
-                            .addChild(R.id.first, secondTrans)
-                            .build();
-                    builder.setCustomDescription(customDescription);
-                })
-                .build());
-
-        // Trigger autofill with custom description
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        final String username = matchFirst ? "marco" : "MARCO";
-        mActivity.onUsername((v) -> v.setText(username));
-        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
-        mActivity.tapLogin();
-
-        final String expectedText = matchFirst ? "polo" : "POLO";
-        assertSaveUiIsShownWithTwoLines(expectedText);
-    }
-
-    @Test
-    public void applyMultipleTransformationsForSameField_matchFirst() throws Exception {
-        multipleTransformationsForSameFieldTest(true);
-    }
-
-    @Test
-    public void applyMultipleTransformationsForSameField_matchSecond() throws Exception {
-        multipleTransformationsForSameFieldTest(false);
-    }
-
-    private RemoteViews newTemplate(int resourceId) {
-        return new RemoteViews(getContext().getPackageName(), resourceId);
-    }
-
-    private UiObject2 assertSaveUiShowing() {
-        try {
-            return mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private void assertSaveUiWithoutCustomDescriptionIsShown() {
-        // First make sure the UI is shown...
-        final UiObject2 saveUi = assertSaveUiShowing();
-
-        // Then make sure it does not have the custom view on it.
-        assertWithMessage("found static_text on SaveUI (%s)", mUiBot.getChildrenAsText(saveUi))
-            .that(saveUi.findObject(By.res(mPackageName, "static_text"))).isNull();
-    }
-
-    private UiObject2 assertSaveUiWithCustomDescriptionIsShown() {
-        // First make sure the UI is shown...
-        final UiObject2 saveUi = assertSaveUiShowing();
-
-        // Then make sure it does have the custom view on it...
-        final UiObject2 staticText = saveUi.findObject(By.res(mPackageName, "static_text"));
-        assertThat(staticText).isNotNull();
-        assertThat(staticText.getText()).isEqualTo("YO:");
-
-        return saveUi;
-    }
-
-    /**
-     * Asserts the save ui only has {@code first} and {@code second} lines (i.e, {@code third} is
-     * invisible), but only {@code first} has text.
-     */
-    private UiObject2 assertSaveUiIsShownWithTwoLines(String expectedTextOnFirst) {
-        return assertSaveUiWithLinesIsShown(
-                (line1) -> assertWithMessage("Wrong text for child with id 'first'")
-                        .that(line1.getText()).isEqualTo(expectedTextOnFirst),
-                (line2) -> assertWithMessage("Wrong text for child with id 'second'")
-                        .that(line2.getText()).isNull(),
-                null);
-    }
-
-    /**
-     * Asserts the save ui only has {@code first} line (i.e., {@code second} and {@code third} are
-     * invisible).
-     */
-    private void assertSaveUiIsShownWithJustOneLine(String expectedTextOnFirst) {
-        assertSaveUiWithLinesIsShown(
-                (line1) -> assertWithMessage("Wrong text for child with id 'first'")
-                        .that(line1.getText()).isEqualTo(expectedTextOnFirst),
-                null, null);
-    }
-
-    private UiObject2 assertSaveUiWithLinesIsShown(@Nullable Visitor<UiObject2> line1Visitor,
-            @Nullable Visitor<UiObject2> line2Visitor, @Nullable Visitor<UiObject2> line3Visitor) {
-        final UiObject2 saveUi = assertSaveUiWithCustomDescriptionIsShown();
-        mUiBot.assertChild(saveUi, "first", line1Visitor);
-        mUiBot.assertChild(saveUi, "second", line2Visitor);
-        mUiBot.assertChild(saveUi, "third", line3Visitor);
-        return saveUi;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java
deleted file mode 100644
index 8432d16..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.BatchUpdates;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.InternalOnClickAction;
-import android.service.autofill.InternalTransformation;
-import android.service.autofill.InternalValidator;
-import android.service.autofill.OnClickAction;
-import android.service.autofill.Transformation;
-import android.service.autofill.Validator;
-import android.util.SparseArray;
-import android.widget.RemoteViews;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class CustomDescriptionUnitTest {
-
-    private final CustomDescription.Builder mBuilder =
-            new CustomDescription.Builder(mock(RemoteViews.class));
-    private final BatchUpdates mValidUpdate =
-            new BatchUpdates.Builder().updateTemplate(mock(RemoteViews.class)).build();
-    private final Transformation mValidTransformation = mock(InternalTransformation.class);
-    private final Validator mValidCondition = mock(InternalValidator.class);
-    private final OnClickAction mValidAction = mock(InternalOnClickAction.class);
-
-    @Test
-    public void testNullConstructor() {
-        assertThrows(NullPointerException.class, () ->  new CustomDescription.Builder(null));
-    }
-
-    @Test
-    public void testAddChild_null() {
-        assertThrows(IllegalArgumentException.class, () ->  mBuilder.addChild(42, null));
-    }
-
-    @Test
-    public void testAddChild_invalidImplementation() {
-        assertThrows(IllegalArgumentException.class,
-                () ->  mBuilder.addChild(42, mock(Transformation.class)));
-    }
-
-    @Test
-    public void testBatchUpdate_nullCondition() {
-        assertThrows(IllegalArgumentException.class,
-                () ->  mBuilder.batchUpdate(null, mValidUpdate));
-    }
-
-    @Test
-    public void testBatchUpdate_invalidImplementation() {
-        assertThrows(IllegalArgumentException.class,
-                () ->  mBuilder.batchUpdate(mock(Validator.class), mValidUpdate));
-    }
-
-    @Test
-    public void testBatchUpdate_nullUpdates() {
-        assertThrows(NullPointerException.class,
-                () ->  mBuilder.batchUpdate(mValidCondition, null));
-    }
-
-    @Test
-    public void testSetOnClickAction_null() {
-        assertThrows(IllegalArgumentException.class, () ->  mBuilder.addOnClickAction(42, null));
-    }
-
-    @Test
-    public void testSetOnClickAction_invalidImplementation() {
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.addOnClickAction(42, mock(OnClickAction.class)));
-    }
-
-    @Test
-    public void testSetOnClickAction_thereCanBeOnlyOne() {
-        final CustomDescription customDescription = mBuilder
-                .addOnClickAction(42, mock(InternalOnClickAction.class))
-                .addOnClickAction(42, mValidAction)
-                .build();
-        final SparseArray<InternalOnClickAction> actions = customDescription.getActions();
-        assertThat(actions.size()).isEqualTo(1);
-        assertThat(actions.keyAt(0)).isEqualTo(42);
-        assertThat(actions.valueAt(0)).isSameAs(mValidAction);
-    }
-
-    @Test
-    public void testBuild_valid() {
-        new CustomDescription.Builder(mock(RemoteViews.class)).build();
-        new CustomDescription.Builder(mock(RemoteViews.class))
-            .addChild(108, mValidTransformation)
-            .batchUpdate(mValidCondition, mValidUpdate)
-            .addOnClickAction(42, mValidAction)
-            .build();
-    }
-
-    @Test
-    public void testNoMoreInteractionsAfterBuild() {
-        mBuilder.build();
-
-        assertThrows(IllegalStateException.class, () -> mBuilder.build());
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.addChild(108, mValidTransformation));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.batchUpdate(mValidCondition, mValidUpdate));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.addOnClickAction(42, mValidAction));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
deleted file mode 100644
index 5806234..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.service.autofill.CustomDescription;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.widget.RemoteViews;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-
-/**
- * Template for tests cases that test what happens when a link in the {@link CustomDescription} is
- * tapped by the user.
- *
- * <p>It must be extend by 2 sub-class to provide tests for the 2 distinct scenarios:
- * <ul>
- *   <li>Save is triggered by 1st activity finishing and launching a 2nd activity.
- *   <li>Save is triggered by explicit {@link android.view.autofill.AutofillManager#commit()} call
- *       and shown in the same activity.
- * </ul>
- *
- * <p>The overall behavior should be the same in both cases, although the implementation of the
- * tests per se will be sligthly different.
- */
-abstract class CustomDescriptionWithLinkTestCase<A extends AbstractAutoFillActivity> extends
-        AutoFillServiceTestCase.AutoActivityLaunch<A> {
-
-    private static final String ID_LINK = "link";
-
-    private final Class<A> mActivityClass;
-
-    protected A mActivity;
-
-    protected CustomDescriptionWithLinkTestCase(@NonNull Class<A> activityClass) {
-        mActivityClass = activityClass;
-    }
-
-    protected void startActivity() {
-        startActivity(false);
-    }
-
-    protected void startActivity(boolean remainOnRecents) {
-        final Intent intent = new Intent(mContext, mActivityClass);
-        if (remainOnRecents) {
-            intent.setFlags(
-                    Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK);
-        }
-        mActivity = launchActivity(intent);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description and then taps back:
-     * the Save UI should have been restored.
-     */
-    @Test
-    public final void testTapLink_tapBack() throws Exception {
-        saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction.TAP_BACK_BUTTON);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, change the screen
-     * orientation while the new activity is show, then taps back:
-     * the Save UI should have been restored.
-     */
-    @Test
-    public final void testTapLink_changeOrientationThenTapBack() throws Exception {
-        assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
-
-        mUiBot.assumeMinimumResolution(500);
-        mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-        try {
-            saveUiRestoredAfterTappingLinkTest(
-                    PostSaveLinkTappedAction.ROTATE_THEN_TAP_BACK_BUTTON);
-        } finally {
-            try {
-                mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-                cleanUpAfterScreenOrientationIsBackToPortrait();
-            } catch (Exception e) {
-                mSafeCleanerRule.add(e);
-            } finally {
-                mUiBot.resetScreenResolution();
-            }
-        }
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, then the new activity
-     * finishes:
-     * the Save UI should have been restored.
-     */
-    @Test
-    public final void testTapLink_finishActivity() throws Exception {
-        saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction.FINISH_ACTIVITY);
-    }
-
-    protected abstract void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception;
-
-    protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and touch outside the Save UI to dismiss it.
-     *
-     * <p>Then user starts a new session by focusing in a field.
-     */
-    @Test
-    public final void testTapLink_tapBack_thenStartOverByTouchOutsideAndFocus()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TOUCH_OUTSIDE, false);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and touch outside the Save UI to dismiss it.
-     *
-     * <p>Then user starts a new session by forcing autofill.
-     */
-    @Test
-    public void testTapLink_tapBack_thenStartOverByTouchOutsideAndManualRequest()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TOUCH_OUTSIDE, true);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and tap the "No" button to dismiss it.
-     *
-     * <p>Then user starts a new session by focusing in a field.
-     */
-    @Test
-    public final void testTapLink_tapBack_thenStartOverBySayingNoAndFocus()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_NO_ON_SAVE_UI,
-                false);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and tap the "No" button to dismiss it.
-     *
-     * <p>Then user starts a new session by forcing autofill.
-     */
-    @Test
-    public final void testTapLink_tapBack_thenStartOverBySayingNoAndManualRequest()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_NO_ON_SAVE_UI, true);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and the "Yes" button to save it.
-     *
-     * <p>Then user starts a new session by focusing in a field.
-     */
-    @Test
-    public final void testTapLink_tapBack_thenStartOverBySayingYesAndFocus()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_YES_ON_SAVE_UI,
-                false);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and the "Yes" button to save it.
-     *
-     * <p>Then user starts a new session by forcing autofill.
-     */
-    @Test
-    public final void testTapLink_tapBack_thenStartOverBySayingYesAndManualRequest()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_YES_ON_SAVE_UI, true);
-    }
-
-    protected abstract void tapLinkThenTapBackThenStartOverTest(
-            PostSaveLinkTappedAction action, boolean manualRequest) throws Exception;
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, then re-launches the
-     * original activity:
-     * the Save UI should have been canceled.
-     */
-    @Test
-    public final void testTapLink_backToPreviousActivityByLaunchingIt()
-            throws Exception {
-        saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_PREVIOUS_ACTIVITY);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, then launches a 3rd
-     * activity:
-     * the Save UI should have been canceled.
-     */
-    @Test
-    public final void testTapLink_launchNewActivityThenTapBack() throws Exception {
-        saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_NEW_ACTIVITY);
-    }
-
-    protected abstract void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception;
-
-    @Test
-    public final void testTapLink_launchTrampolineActivityThenTapBackAndStartNewSession()
-            throws Exception {
-        tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest();
-    }
-
-    protected abstract void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
-            throws Exception;
-
-    @Test
-    public final void testTapLinkAfterUpdateAppliedToLinkView() throws Exception {
-        tapLinkAfterUpdateAppliedTest(true);
-    }
-
-    @Test
-    public final void testTapLinkAfterUpdateAppliedToAnotherView() throws Exception {
-        tapLinkAfterUpdateAppliedTest(false);
-    }
-
-    protected abstract void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception;
-
-    enum PostSaveLinkTappedAction {
-        TAP_BACK_BUTTON,
-        ROTATE_THEN_TAP_BACK_BUTTON,
-        FINISH_ACTIVITY,
-        LAUNCH_NEW_ACTIVITY,
-        LAUNCH_PREVIOUS_ACTIVITY,
-        TOUCH_OUTSIDE,
-        TAP_NO_ON_SAVE_UI,
-        TAP_YES_ON_SAVE_UI
-    }
-
-    protected final void startActivityOnNewTask(Class<?> clazz) {
-        final Intent intent = new Intent(mContext, clazz);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(intent);
-    }
-
-    protected RemoteViews newTemplate() {
-        final RemoteViews presentation = new RemoteViews(mPackageName,
-                R.layout.custom_description_with_link);
-        return presentation;
-    }
-
-    protected final CustomDescription.Builder newCustomDescriptionBuilder(
-            Class<? extends Activity> activityClass) {
-        final Intent intent = new Intent(mContext, activityClass);
-        intent.setFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
-        return newCustomDescriptionBuilder(intent);
-    }
-
-    protected final CustomDescription newCustomDescription(
-            Class<? extends Activity> activityClass) {
-        return newCustomDescriptionBuilder(activityClass).build();
-    }
-
-    protected final CustomDescription.Builder newCustomDescriptionBuilder(Intent intent) {
-        final RemoteViews presentation = newTemplate();
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
-        presentation.setOnClickPendingIntent(R.id.link, pendingIntent);
-        return new CustomDescription.Builder(presentation);
-    }
-
-    protected final CustomDescription newCustomDescription(Intent intent) {
-        return newCustomDescriptionBuilder(intent).build();
-    }
-
-    protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType) throws Exception {
-        return assertSaveUiWithLinkIsShown(saveType, "DON'T TAP ME!");
-    }
-
-    protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType, String expectedText)
-            throws Exception {
-        // First make sure the UI is shown...
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(saveType);
-        // Then make sure it does have the custom view with link on it...
-        final UiObject2 link = getLink(saveUi);
-        assertThat(link.getText()).isEqualTo(expectedText);
-        return saveUi;
-    }
-
-    protected final UiObject2 getLink(final UiObject2 container) {
-        final UiObject2 link = container.findObject(By.res(mPackageName, ID_LINK));
-        assertThat(link).isNotNull();
-        return link;
-    }
-
-    protected final void tapSaveUiLink(UiObject2 saveUi) {
-        getLink(saveUi).click();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringDropdownTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringDropdownTest.java
deleted file mode 100644
index 3839d63..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringDropdownTest.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-public class DatasetFilteringDropdownTest extends DatasetFilteringTest {
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
deleted file mode 100644
index 6893090..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
+++ /dev/null
@@ -1,671 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Timeouts.MOCK_IME_TIMEOUT_MS;
-
-import static com.android.compatibility.common.util.ShellUtils.sendKeyEvent;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.content.IntentSender;
-import android.os.Process;
-import android.platform.test.annotations.AppModeFull;
-import android.view.KeyEvent;
-import android.widget.EditText;
-
-import com.android.cts.mockime.ImeCommand;
-import com.android.cts.mockime.ImeEventStream;
-import com.android.cts.mockime.MockImeSession;
-
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-
-import java.util.regex.Pattern;
-
-public abstract class DatasetFilteringTest extends AbstractLoginActivityTestCase {
-
-    protected DatasetFilteringTest() {
-    }
-
-    protected DatasetFilteringTest(UiBot inlineUiBot) {
-        super(inlineUiBot);
-    }
-
-    @Override
-    protected TestRule getMainTestRule() {
-        return RuleChain.outerRule(new MaxVisibleDatasetsRule(4))
-                        .around(super.getMainTestRule());
-    }
-
-
-    private void changeUsername(CharSequence username) {
-        mActivity.onUsername((v) -> v.setText(username));
-    }
-
-
-    @Test
-    public void testFilter() throws Exception {
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "b")
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // Only two datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // Only one dataset start with 'aa'
-        changeUsername("aa");
-        mUiBot.assertDatasets(aa);
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        changeUsername("aaa");
-        callback.assertUiHiddenEvent(mActivity.getUsername());
-        mUiBot.assertNoDatasets();
-
-        // Delete some text to bring back 2 datasets
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // With no filter text all datasets should be shown again
-        changeUsername("");
-        mUiBot.assertDatasets(aa, ab, b);
-    }
-
-    @Test
-    public void testFilter_injectingEvents() throws Exception {
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "b")
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // Only two datasets start with 'a'
-        sendKeyEvent("KEYCODE_A");
-        mUiBot.assertDatasets(aa, ab);
-
-        // Only one dataset start with 'aa'
-        sendKeyEvent("KEYCODE_A");
-        mUiBot.assertDatasets(aa);
-
-        // Only two datasets start with 'a'
-        sendKeyEvent("KEYCODE_DEL");
-        mUiBot.assertDatasets(aa, ab);
-
-        // With no filter text all datasets should be shown
-        sendKeyEvent("KEYCODE_DEL");
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        sendKeyEvent("KEYCODE_A");
-        sendKeyEvent("KEYCODE_A");
-        sendKeyEvent("KEYCODE_A");
-        callback.assertUiHiddenEvent(mActivity.getUsername());
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    public void testFilter_usingKeyboard() throws Exception {
-        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
-        assumeTrue("MockIME not available", mockImeSession != null);
-
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "b")
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        final ImeEventStream stream = mockImeSession.openEventStream();
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-
-        // Wait until the MockIme gets bound to the TestActivity.
-        expectBindInput(stream, Process.myPid(), MOCK_IME_TIMEOUT_MS);
-        expectEvent(stream, editorMatcher("onStartInput", mActivity.getUsername().getId()),
-                MOCK_IME_TIMEOUT_MS);
-
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // Only two datasets start with 'a'
-        final ImeCommand cmd1 = mockImeSession.callCommitText("a", 1);
-        expectCommand(stream, cmd1, MOCK_IME_TIMEOUT_MS);
-        mUiBot.assertDatasets(aa, ab);
-
-        // Only one dataset start with 'aa'
-        final ImeCommand cmd2 = mockImeSession.callCommitText("a", 1);
-        expectCommand(stream, cmd2, MOCK_IME_TIMEOUT_MS);
-        mUiBot.assertDatasets(aa);
-
-        // Only two datasets start with 'a'
-        final ImeCommand cmd3 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
-        expectCommand(stream, cmd3, MOCK_IME_TIMEOUT_MS);
-        mUiBot.assertDatasets(aa, ab);
-
-        // With no filter text all datasets should be shown
-        final ImeCommand cmd4 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
-        expectCommand(stream, cmd4, MOCK_IME_TIMEOUT_MS);
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final ImeCommand cmd5 = mockImeSession.callCommitText("aaa", 1);
-        expectCommand(stream, cmd5, MOCK_IME_TIMEOUT_MS);
-        callback.assertUiHiddenEvent(mActivity.getUsername());
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_nullValuesAlwaysMatched() throws Exception {
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, (String) null)
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // Two datasets start with 'a' and one with null value always shown
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // One dataset start with 'aa' and one with null value always shown
-        changeUsername("aa");
-        mUiBot.assertDatasets(aa, b);
-
-        // Two datasets start with 'a' and one with null value always shown
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // With no filter text all datasets should be shown
-        changeUsername("");
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // No dataset start with 'aaa' and one with null value always shown
-        changeUsername("aaa");
-        mUiBot.assertDatasets(b);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_differentPrefixes() throws Exception {
-        final String a = "aaa";
-        final String b = "bra";
-        final String c = "cadabra";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, a)
-                        .setPresentation(a, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, b)
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, c)
-                        .setPresentation(c, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(a, b, c);
-
-        changeUsername("a");
-        mUiBot.assertDatasets(a);
-
-        changeUsername("b");
-        mUiBot.assertDatasets(b);
-
-        changeUsername("c");
-        if (!isInlineMode()) { // With inline, we don't show the datasets now to protect privacy.
-            mUiBot.assertDatasets(c);
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_usingRegex() throws Exception {
-        // Dataset presentations.
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "whatever", Pattern.compile("a|aa"))
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "whatsoever",
-                                Pattern.compile("a|ab"))
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, (String) null, Pattern.compile("b"))
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // Only two datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // Only one dataset start with 'aa'
-        changeUsername("aa");
-        mUiBot.assertDatasets(aa);
-
-        // Only two datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // With no filter text all datasets should be shown
-        changeUsername("");
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        changeUsername("aaa");
-        callback.assertUiHiddenEvent(mActivity.getUsername());
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_disabledUsingNullRegex() throws Exception {
-        // Dataset presentations.
-        final String unfilterable = "Unfilterabled";
-        final String aOrW = "A or W";
-        final String w = "Wazzup";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                // This dataset has a value but filter is disabled
-                .addDataset(new CannedDataset.Builder()
-                        .setUnfilterableField(ID_USERNAME, "a am I")
-                        .setPresentation(unfilterable, isInlineMode())
-                        .build())
-                // This dataset uses pattern to filter
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "whatsoever",
-                                Pattern.compile("a|aw"))
-                        .setPresentation(aOrW, isInlineMode())
-                        .build())
-                // This dataset uses value to filter
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "wazzup")
-                        .setPresentation(w, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(unfilterable, aOrW, w);
-
-        // Only one dataset start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aOrW);
-
-        // No dataset starts with 'aa'
-        changeUsername("aa");
-        mUiBot.assertNoDatasets();
-
-        // Only one datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aOrW);
-
-        // With no filter text all datasets should be shown
-        changeUsername("");
-        mUiBot.assertDatasets(unfilterable, aOrW, w);
-
-        // Only one datasets start with 'w'
-        changeUsername("w");
-        if (!isInlineMode()) { // With inline, we don't show the datasets now to protect privacy.
-            mUiBot.assertDatasets(w);
-        }
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        changeUsername("aaa");
-        callback.assertUiHiddenEvent(mActivity.getUsername());
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_mixPlainAndRegex() throws Exception {
-        final String plain = "Plain";
-        final String regexPlain = "RegexPlain";
-        final String authRegex = "AuthRegex";
-        final String kitchnSync = "KitchenSync";
-        final Pattern everything = Pattern.compile(".*");
-
-        enableService();
-
-        // Set expectations.
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .build());
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aword")
-                        .setPresentation(plain, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "a ignore", everything)
-                        .setPresentation(regexPlain, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab ignore", everything)
-                        .setAuthentication(authentication)
-                        .setPresentation(authRegex, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab ignore",
-                                everything)
-                        .setPresentation(kitchnSync, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
-
-        // All datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
-
-        // Only the regex datasets should start with 'ab'
-        changeUsername("ab");
-        mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter_usingKeyboard() is enough")
-    public void testFilter_mixPlainAndRegex_usingKeyboard() throws Exception {
-        final String plain = "Plain";
-        final String regexPlain = "RegexPlain";
-        final String authRegex = "AuthRegex";
-        final String kitchnSync = "KitchenSync";
-        final Pattern everything = Pattern.compile(".*");
-
-        enableService();
-
-        // Set expectations.
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .build());
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aword")
-                        .setPresentation(plain, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "a ignore", everything)
-                        .setPresentation(regexPlain, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab ignore", everything)
-                        .setAuthentication(authentication)
-                        .setPresentation(authRegex, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab ignore",
-                                everything)
-                        .setPresentation(kitchnSync, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
-
-        // All datasets start with 'a'
-        sendKeyEvent("KEYCODE_A");
-        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
-
-        // Only the regex datasets should start with 'ab'
-        sendKeyEvent("KEYCODE_B");
-        mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_resetFilter_chooseFirst() throws Exception {
-        resetFilterTest(1);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_resetFilter_chooseSecond() throws Exception {
-        resetFilterTest(2);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_resetFilter_chooseThird() throws Exception {
-        resetFilterTest(3);
-    }
-
-    // Tests that datasets are re-shown and filtering still works after clearing a selected value.
-    private void resetFilterTest(int number) throws Exception {
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "b")
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        final String chosenOne;
-        switch (number) {
-            case 1:
-                chosenOne = aa;
-                mActivity.expectAutoFill("aa");
-                break;
-            case 2:
-                chosenOne = ab;
-                mActivity.expectAutoFill("ab");
-                break;
-            case 3:
-                chosenOne = b;
-                mActivity.expectAutoFill("b");
-                break;
-            default:
-                throw new IllegalArgumentException("invalid dataset number: " + number);
-        }
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText username = mActivity.getUsername();
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        callback.assertUiShownEvent(username);
-
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // select the choice
-        mUiBot.selectDataset(chosenOne);
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Change the filled text and check that filtering still works.
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // Reset back to all choices
-        changeUsername("");
-        mUiBot.assertDatasets(aa, ab, b);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java
deleted file mode 100644
index 48af2cc..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.testng.Assert.assertThrows;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceSpec;
-import android.net.Uri;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.Dataset;
-import android.service.autofill.InlinePresentation;
-import android.util.Size;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.RemoteViews;
-import android.widget.inline.InlinePresentationSpec;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.regex.Pattern;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class DatasetTest {
-
-    private final AutofillId mId = new AutofillId(42);
-    private final AutofillValue mValue = AutofillValue.forText("ValuableLikeGold");
-    private final Pattern mFilter = Pattern.compile("whatever");
-    private final InlinePresentation mInlinePresentation = new InlinePresentation(
-            new Slice.Builder(new Uri.Builder().appendPath("DatasetTest").build(),
-                    new SliceSpec("DatasetTest", 1)).build(),
-            new InlinePresentationSpec.Builder(new Size(10, 10),
-                    new Size(50, 50)).build(), /* pinned= */ false);
-
-    private final RemoteViews mPresentation = mock(RemoteViews.class);
-
-    @Test
-    public void testBuilder_nullPresentation() {
-        assertThrows(NullPointerException.class, () -> new Dataset.Builder((RemoteViews) null));
-    }
-
-    @Test
-    public void testBuilder_nullInlinePresentation() {
-        assertThrows(NullPointerException.class,
-                () -> new Dataset.Builder((InlinePresentation) null));
-    }
-
-    @Test
-    public void testBuilder_validPresentations() {
-        assertThat(new Dataset.Builder(mPresentation)).isNotNull();
-        assertThat(new Dataset.Builder(mInlinePresentation)).isNotNull();
-    }
-
-    @Test
-    public void testBuilder_setNullInlinePresentation() {
-        final Dataset.Builder builder = new Dataset.Builder(mPresentation);
-        assertThrows(NullPointerException.class, () -> builder.setInlinePresentation(null));
-    }
-
-    @Test
-    public void testBuilder_setInlinePresentation() {
-        assertThat(new Dataset.Builder().setInlinePresentation(mInlinePresentation)).isNotNull();
-    }
-
-    @Test
-    public void testBuilder_setValueNullId() {
-        final Dataset.Builder builder = new Dataset.Builder(mPresentation);
-        assertThrows(NullPointerException.class, () -> builder.setValue(null, mValue));
-    }
-
-    @Test
-    public void testBuilder_setValueWithoutPresentation() {
-        // Just assert that it builds without throwing an exception.
-        assertThat(new Dataset.Builder().setValue(mId, mValue).build()).isNotNull();
-    }
-
-    @Test
-    public void testBuilder_setValueWithNullPresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
-                (RemoteViews) null));
-    }
-
-    @Test
-    public void testBuilder_setValueWithBothPresentation_nullPresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
-                null, mInlinePresentation));
-    }
-
-    @Test
-    public void testBuilder_setValueWithBothPresentation_nullInlinePresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
-                mPresentation, null));
-    }
-
-    @Test
-    public void testBuilder_setValueWithBothPresentation_bothNull() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
-                (RemoteViews) null, null));
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithNullFilter() {
-        assertThat(new Dataset.Builder(mPresentation).setValue(mId, mValue, (Pattern) null).build())
-                .isNotNull();
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithPresentation_nullFilter() {
-        assertThat(new Dataset.Builder().setValue(mId, mValue, null, mPresentation).build())
-                .isNotNull();
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithPresentation_nullPresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
-                null));
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithoutPresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue, mFilter));
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithBothPresentation_nullPresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
-                null, mInlinePresentation));
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithBothPresentation_nullInlinePresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
-                mPresentation, null));
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithBothPresentation_bothNull() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
-                null, null));
-    }
-
-    @Test
-    public void testBuilder_setFieldInlinePresentations() {
-        assertThat(new Dataset.Builder().setFieldInlinePresentation(mId, mValue, mFilter,
-                mInlinePresentation)).isNotNull();
-    }
-
-    @Test
-    public void testBuild_noValues() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(IllegalStateException.class, () -> builder.build());
-    }
-
-    @Test
-    public void testNoMoreInteractionsAfterBuild() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        builder.setValue(mId, mValue, mPresentation);
-        assertThat(builder.build()).isNotNull();
-        assertThrows(IllegalStateException.class, () -> builder.build());
-        assertThrows(IllegalStateException.class,
-                () -> builder.setInlinePresentation(mInlinePresentation));
-        assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setValue(mId, mValue, mPresentation));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setValue(mId, mValue, mFilter));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setValue(mId, mValue, mFilter, mPresentation));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setValue(mId, mValue, mPresentation, mInlinePresentation));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setValue(mId, mValue, mFilter, mPresentation, mInlinePresentation));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setFieldInlinePresentation(mId, mValue, mFilter,
-                        mInlinePresentation));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivity.java
deleted file mode 100644
index 4873c39..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-public class DatePickerCalendarActivity extends AbstractDatePickerActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.date_picker_calendar_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java
deleted file mode 100644
index decc4b7..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "Unit test")
-public class DatePickerCalendarActivityTest extends DatePickerTestCase<DatePickerCalendarActivity> {
-
-    @Override
-    protected AutofillActivityTestRule<DatePickerCalendarActivity> getActivityRule() {
-        return new AutofillActivityTestRule<DatePickerCalendarActivity>(
-                DatePickerCalendarActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivity.java
deleted file mode 100644
index c9d39f8..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-public class DatePickerSpinnerActivity extends AbstractDatePickerActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.date_picker_spinner_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java
deleted file mode 100644
index 4b63e10..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "Unit test")
-public class DatePickerSpinnerActivityTest extends DatePickerTestCase<DatePickerSpinnerActivity> {
-
-    @Override
-    protected AutofillActivityTestRule<DatePickerSpinnerActivity> getActivityRule() {
-        return new AutofillActivityTestRule<DatePickerSpinnerActivity>(
-                DatePickerSpinnerActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
deleted file mode 100644
index 13cb12b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.AbstractDatePickerActivity.ID_DATE_PICKER;
-import static android.autofillservice.cts.AbstractDatePickerActivity.ID_OUTPUT;
-import static android.autofillservice.cts.Helper.assertDateValue;
-import static android.autofillservice.cts.Helper.assertNumberOfChildren;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.icu.util.Calendar;
-
-import org.junit.Test;
-
-/**
- * Base class for {@link AbstractDatePickerActivity} tests.
- */
-abstract class DatePickerTestCase<A extends AbstractDatePickerActivity>
-        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
-
-    protected A mActivity;
-
-    @Test
-    public void testAutoFillAndSave() throws Exception {
-        assertWithMessage("subclass did not set mActivity").that(mActivity).isNotNull();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Calendar cal = Calendar.getInstance();
-        cal.set(Calendar.YEAR, 2012);
-        cal.set(Calendar.MONTH, Calendar.DECEMBER);
-        cal.set(Calendar.DAY_OF_MONTH, 20);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                    .setPresentation(createPresentation("The end of the world"))
-                    .setField(ID_OUTPUT, "Y U NO CHANGE ME?")
-                    .setField(ID_DATE_PICKER, cal.getTimeInMillis())
-                    .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER)
-                .build());
-        mActivity.expectAutoFill("2012/11/20", 2012, Calendar.DECEMBER, 20);
-
-        // Trigger auto-fill.
-        mActivity.onOutput((v) -> v.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert properties of DatePicker field.
-        assertTextIsSanitized(fillRequest.structure, ID_DATE_PICKER);
-        assertNumberOfChildren(fillRequest.structure, ID_DATE_PICKER, 0);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The end of the world");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Trigger save.
-        mActivity.setDate(2010, Calendar.DECEMBER, 12);
-        mActivity.tapOk();
-
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
-
-        // Assert sanitization on save: everything should be available!
-        assertDateValue(findNodeByResourceId(saveRequest.structure, ID_DATE_PICKER), 2010, 11, 12);
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_OUTPUT), "2010/11/12");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DateTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/DateTransformationTest.java
deleted file mode 100644
index c6385ed..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DateTransformationTest.java
+++ /dev/null
@@ -1,99 +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.autofillservice.cts;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.icu.text.SimpleDateFormat;
-import android.icu.util.Calendar;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.DateTransformation;
-import android.service.autofill.ValueFinder;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.RemoteViews;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class DateTransformationTest {
-
-    @Mock private ValueFinder mValueFinder;
-    @Mock private RemoteViews mTemplate;
-
-    private final AutofillId mFieldId = new AutofillId(42);
-
-    @Test
-    public void testConstructor_nullFieldId() {
-        assertThrows(NullPointerException.class,
-                () -> new DateTransformation(null, new SimpleDateFormat()));
-    }
-
-    @Test
-    public void testConstructor_nullDateFormat() {
-        assertThrows(NullPointerException.class, () -> new DateTransformation(mFieldId, null));
-    }
-
-    @Test
-    public void testFieldNotFound() throws Exception {
-        final DateTransformation trans = new DateTransformation(mFieldId, new SimpleDateFormat());
-
-        trans.apply(mValueFinder, mTemplate, 0);
-
-        verify(mTemplate, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void testInvalidAutofillValueType() throws Exception {
-        final DateTransformation trans = new DateTransformation(mFieldId, new SimpleDateFormat());
-
-        when(mValueFinder.findRawValueByAutofillId(mFieldId))
-                .thenReturn(AutofillValue.forText("D'OH"));
-        trans.apply(mValueFinder, mTemplate, 0);
-
-        verify(mTemplate, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void testValidAutofillValue() throws Exception {
-        final DateTransformation trans = new DateTransformation(mFieldId,
-                new SimpleDateFormat("MM/yyyy"));
-
-        final Calendar cal = Calendar.getInstance();
-        cal.set(Calendar.YEAR, 2012);
-        cal.set(Calendar.MONTH, Calendar.DECEMBER);
-        cal.set(Calendar.DAY_OF_MONTH, 20);
-
-        when(mValueFinder.findRawValueByAutofillId(mFieldId))
-                .thenReturn(AutofillValue.forDate(cal.getTimeInMillis()));
-
-        trans.apply(mValueFinder, mTemplate, 0);
-
-        verify(mTemplate).setCharSequence(eq(0), any(),
-                argThat(new CharSequenceMatcher("12/2012")));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DateValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/DateValueSanitizerTest.java
deleted file mode 100644
index fc9f1dd..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DateValueSanitizerTest.java
+++ /dev/null
@@ -1,88 +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.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.icu.text.SimpleDateFormat;
-import android.icu.util.Calendar;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.DateValueSanitizer;
-import android.util.Log;
-import android.view.autofill.AutofillValue;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Date;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class DateValueSanitizerTest {
-
-    private static final String TAG = "DateValueSanitizerTest";
-
-    private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MM/yyyy");
-
-    @Test
-    public void testConstructor_nullDateFormat() {
-        assertThrows(NullPointerException.class, () -> new DateValueSanitizer(null));
-    }
-
-    @Test
-    public void testSanitize_nullValue() throws Exception {
-        final DateValueSanitizer sanitizer = new DateValueSanitizer(new SimpleDateFormat());
-        assertThat(sanitizer.sanitize(null)).isNull();
-    }
-
-    @Test
-    public void testSanitize_invalidValue() throws Exception {
-        final DateValueSanitizer sanitizer = new DateValueSanitizer(new SimpleDateFormat());
-        assertThat(sanitizer.sanitize(AutofillValue.forText("D'OH!"))).isNull();
-    }
-
-    @Test
-    public void testSanitize_ok() throws Exception {
-        final Calendar inputCal = Calendar.getInstance();
-        inputCal.set(Calendar.YEAR, 2012);
-        inputCal.set(Calendar.MONTH, Calendar.DECEMBER);
-        inputCal.set(Calendar.DAY_OF_MONTH, 20);
-        final long inputDate = inputCal.getTimeInMillis();
-        final AutofillValue inputValue = AutofillValue.forDate(inputDate);
-        Log.v(TAG, "Input date: " + inputDate + " >> " + new Date(inputDate));
-
-        final Calendar expectedCal = Calendar.getInstance();
-        expectedCal.clear(); // We just care for year and month...
-        expectedCal.set(Calendar.YEAR, 2012);
-        expectedCal.set(Calendar.MONTH, Calendar.DECEMBER);
-        final long expectedDate = expectedCal.getTimeInMillis();
-        final AutofillValue expectedValue = AutofillValue.forDate(expectedDate);
-        Log.v(TAG, "Exected date: " + expectedDate + " >> " + new Date(expectedDate));
-
-        final DateValueSanitizer sanitizer = new DateValueSanitizer(
-                mDateFormat);
-        final AutofillValue sanitizedValue = sanitizer.sanitize(inputValue);
-        final long sanitizedDate = sanitizedValue.getDateValue();
-        Log.v(TAG, "Sanitized date: " + sanitizedDate + " >> " + new Date(sanitizedDate));
-        assertThat(sanitizedDate).isEqualTo(expectedDate);
-        assertThat(sanitizedValue).isEqualTo(expectedValue);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java
deleted file mode 100644
index 6d36b72..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.EditText;
-
-/**
- * Activity that has buttons to launch dialogs that should then be autofillable.
- */
-public class DialogLauncherActivity extends AbstractAutoFillActivity {
-
-    private FillExpectation mExpectation;
-    private LoginDialog mDialog;
-    Button mLaunchButton;
-
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.dialog_launcher_activity);
-        mLaunchButton = findViewById(R.id.launch_button);
-        mDialog = new LoginDialog(this);
-        mLaunchButton.setOnClickListener((v) -> mDialog.show());
-    }
-
-    void onUsername(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mDialog.mUsernameEditText));
-    }
-
-    void launchDialog(UiBot uiBot) throws Exception {
-        syncRunOnUiThread(() -> mLaunchButton.performClick());
-        // TODO: should assert by id, but it's not working
-        uiBot.assertShownByText("Username");
-    }
-
-    void assertInDialogBounds(Rect rect) {
-        final int[] location = new int[2];
-        final View view = mDialog.getWindow().getDecorView();
-        view.getLocationOnScreen(location);
-        assertThat(location[0]).isAtMost(rect.left);
-        assertThat(rect.right).isAtMost(location[0] + view.getWidth());
-        assertThat(location[1]).isAtMost(rect.top);
-        assertThat(rect.bottom).isAtMost(location[1] + view.getHeight());
-    }
-
-    void maximizeDialog() {
-        final WindowManager wm = getWindowManager();
-        final Display display = wm.getDefaultDisplay();
-        final DisplayMetrics metrics = new DisplayMetrics();
-        display.getMetrics(metrics);
-        syncRunOnUiThread(
-                () -> mDialog.getWindow().setLayout(metrics.widthPixels, metrics.heightPixels));
-    }
-
-    void expectAutofill(String username, String password) {
-        assertWithMessage("must call launchDialog first").that(mDialog.mUsernameEditText)
-                .isNotNull();
-        mExpectation = new FillExpectation(username, password);
-        mDialog.mUsernameEditText.addTextChangedListener(mExpectation.mCcUsernameWatcher);
-        mDialog.mPasswordEditText.addTextChangedListener(mExpectation.mCcPasswordWatcher);
-    }
-
-    void assertAutofilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        if (mExpectation.mCcUsernameWatcher != null) {
-            mExpectation.mCcUsernameWatcher.assertAutoFilled();
-        }
-        if (mExpectation.mCcPasswordWatcher != null) {
-            mExpectation.mCcPasswordWatcher.assertAutoFilled();
-        }
-    }
-
-    private final class FillExpectation {
-        private final OneTimeTextWatcher mCcUsernameWatcher;
-        private final OneTimeTextWatcher mCcPasswordWatcher;
-
-        private FillExpectation(String username, String password) {
-            mCcUsernameWatcher = username == null ? null
-                    : new OneTimeTextWatcher("username", mDialog.mUsernameEditText, username);
-            mCcPasswordWatcher = password == null ? null
-                    : new OneTimeTextWatcher("password", mDialog.mPasswordEditText, password);
-        }
-
-        private FillExpectation(String username) {
-            this(username, null);
-        }
-    }
-
-    public final class LoginDialog extends AlertDialog {
-
-        private EditText mUsernameEditText;
-        private EditText mPasswordEditText;
-
-        public LoginDialog(Context context) {
-            super(context);
-        }
-
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-
-            setContentView(R.layout.login_activity);
-            mUsernameEditText = findViewById(R.id.username);
-            mPasswordEditText = findViewById(R.id.password);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java
deleted file mode 100644
index 175d0cb..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-
-import org.junit.Test;
-
-public class DialogLauncherActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<DialogLauncherActivity> {
-
-    private DialogLauncherActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<DialogLauncherActivity> getActivityRule() {
-        return new AutofillActivityTestRule<DialogLauncherActivity>(DialogLauncherActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testAutofill_noDatasets() throws Exception {
-        autofillNoDatasetsTest(false);
-    }
-
-    @Test
-    public void testAutofill_noDatasets_afterResizing() throws Exception {
-        autofillNoDatasetsTest(true);
-    }
-
-    private void autofillNoDatasetsTest(boolean resize) throws Exception {
-        enableService();
-        mActivity.launchDialog(mUiBot);
-
-        if (resize) {
-            mActivity.maximizeDialog();
-        }
-
-        // Set expectations.
-        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Asserts results.
-        try {
-            mUiBot.assertNoDatasetsEver();
-            // Make sure nodes were properly generated.
-            assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
-            assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
-        } catch (AssertionError e) {
-            Helper.dumpStructure("D'OH!", fillRequest.structure);
-            throw e;
-        }
-    }
-
-    @Test
-    public void testAutofill_oneDataset() throws Exception {
-        autofillOneDatasetTest(false);
-    }
-
-    @Test
-    public void testAutofill_oneDataset_afterResizing() throws Exception {
-        autofillOneDatasetTest(true);
-    }
-
-    private void autofillOneDatasetTest(boolean resize) throws Exception {
-        enableService();
-        mActivity.launchDialog(mUiBot);
-
-        if (resize) {
-            mActivity.maximizeDialog();
-        }
-
-        // Set expectations.
-        mActivity.expectAutofill("dude", "sweet");
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        final UiObject2 picker = mUiBot.assertDatasets("The Dude");
-        if (!Helper.isAutofillWindowFullScreen(mActivity)) {
-            mActivity.assertInDialogBounds(picker.getVisibleBounds());
-        }
-
-        // Asserts results.
-        mUiBot.selectDataset("The Dude");
-        mActivity.assertAutofilled();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java b/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
deleted file mode 100644
index f86c752..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.ACTIVITY_RESURRECTION;
-import static android.autofillservice.cts.Timeouts.CALLBACK_NOT_CALLED_TIMEOUT_MS;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillResponse;
-import android.util.Log;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import org.junit.Test;
-
-/**
- * Tests for the {@link android.service.autofill.FillResponse.Builder#disableAutofill(long)} API.
- */
-public class DisableAutofillTest extends AutoFillServiceTestCase.ManualActivityLaunch {
-
-    private static final String TAG = "DisableAutofillTest";
-
-    /**
-     * Defines what to do after the activity being tested is launched.
-     */
-    enum PostLaunchAction {
-        /**
-         * Used when the service disables autofill in the fill response for this activty. As such:
-         *
-         * <ol>
-         *   <li>There should be a fill request on {@code sReplier}.
-         *   <li>The first UI focus should generate a
-         *   {@link android.view.autofill.AutofillManager.AutofillCallback#EVENT_INPUT_UNAVAILABLE}
-         *   event.
-         *   <li>Subsequent UI focus should not trigger events.
-         * </ol>
-         */
-        ASSERT_DISABLING,
-
-        /**
-         * Used when the service already disabled autofill prior to launching activty. As such:
-         *
-         * <ol>
-         *   <li>There should be no fill request on {@code sReplier}.
-         *   <li>There should be no callback calls when UI is focused
-         * </ol>
-         */
-        ASSERT_DISABLED,
-
-        /**
-         * Used when autofill is enabled, so it tries to autofill the activity.
-         */
-        ASSERT_ENABLED_AND_AUTOFILL
-    }
-
-    /**
-     * Launches and finishes {@link SimpleSaveActivity}, returning how long it took.
-     */
-    private long launchSimpleSaveActivity(PostLaunchAction action) throws Exception {
-        Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
-        sReplier.assertNoUnhandledFillRequests();
-
-        if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
-            sReplier.addResponse(new CannedFillResponse.Builder()
-                    .addDataset(new CannedDataset.Builder()
-                            .setField(SimpleSaveActivity.ID_INPUT, "id")
-                            .setField(SimpleSaveActivity.ID_PASSWORD, "pass")
-                            .setPresentation(createPresentation("YO"))
-                            .build())
-                    .build());
-
-        }
-
-        final long before = SystemClock.elapsedRealtime();
-        final SimpleSaveActivity activity = startSimpleSaveActivity();
-        final MyAutofillCallback callback = activity.registerCallback();
-
-        try {
-            // Trigger autofill
-            activity.syncRunOnUiThread(() -> activity.mInput.requestFocus());
-
-            if (action == PostLaunchAction.ASSERT_DISABLING) {
-                callback.assertUiUnavailableEvent(activity.mInput);
-                sReplier.getNextFillRequest();
-
-                // Make sure other fields are not triggered.
-                activity.syncRunOnUiThread(() -> activity.mPassword.requestFocus());
-                callback.assertNotCalled();
-            } else if (action == PostLaunchAction.ASSERT_DISABLED) {
-                // Make sure forced requests are ignored as well.
-                activity.getAutofillManager().requestAutofill(activity.mInput);
-                callback.assertNotCalled();
-            } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
-                callback.assertUiShownEvent(activity.mInput);
-                sReplier.getNextFillRequest();
-                final SimpleSaveActivity.FillExpectation autofillExpectation =
-                        activity.expectAutoFill("id", "pass");
-                mUiBot.selectDataset("YO");
-                autofillExpectation.assertAutoFilled();
-            }
-
-            // Asserts isEnabled() status.
-            assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-        } finally {
-            activity.finish();
-        }
-        return SystemClock.elapsedRealtime() - before;
-    }
-
-    /**
-     * Launches and finishes {@link PreSimpleSaveActivity}, returning how long it took.
-     */
-    private long launchPreSimpleSaveActivity(PostLaunchAction action) throws Exception {
-        Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
-        sReplier.assertNoUnhandledFillRequests();
-
-        if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
-            sReplier.addResponse(new CannedFillResponse.Builder()
-                    .addDataset(new CannedDataset.Builder()
-                            .setField(PreSimpleSaveActivity.ID_PRE_INPUT, "yo")
-                            .setPresentation(createPresentation("YO"))
-                            .build())
-                    .build());
-        }
-
-        final long before = SystemClock.elapsedRealtime();
-        final PreSimpleSaveActivity activity = startPreSimpleSaveActivity();
-        final MyAutofillCallback callback = activity.registerCallback();
-
-        try {
-            // Trigger autofill
-            activity.syncRunOnUiThread(() -> activity.mPreInput.requestFocus());
-
-            if (action == PostLaunchAction.ASSERT_DISABLING) {
-                callback.assertUiUnavailableEvent(activity.mPreInput);
-                sReplier.getNextFillRequest();
-            } else if (action == PostLaunchAction.ASSERT_DISABLED) {
-                activity.getAutofillManager().requestAutofill(activity.mPreInput);
-                callback.assertNotCalled();
-            } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
-                callback.assertUiShownEvent(activity.mPreInput);
-                sReplier.getNextFillRequest();
-                final PreSimpleSaveActivity.FillExpectation autofillExpectation =
-                        activity.expectAutoFill("yo");
-                mUiBot.selectDataset("YO");
-                autofillExpectation.assertAutoFilled();
-            }
-
-            // Asserts isEnabled() status.
-            assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-        } finally {
-            activity.finish();
-        }
-        return SystemClock.elapsedRealtime() - before;
-    }
-
-    @Test
-    public void testDisableApp() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(
-                new CannedFillResponse.Builder().disableAutofill(Long.MAX_VALUE).build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-
-        // Launch activity again.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Now try it using a different activity - should be disabled too.
-        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDisableApp() is enough")
-    public void testDisableAppThenWaitToReenableIt() throws Exception {
-        // Set service.
-        enableService();
-
-        // Need to wait the equivalent of launching 2 activities, plus some extra legging room
-        final long duration = 2 * ACTIVITY_RESURRECTION.ms() + 500;
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder().disableAutofill(duration).build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-
-        // Launch activity again.
-        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Wait for the timeout, then try again, autofilling it this time.
-        sleep(passedTime, duration);
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-
-        // Also try it on another activity.
-        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDisableApp() is enough")
-    public void testDisableAppThenResetServiceToReenableIt() throws Exception {
-        enableService();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .disableAutofill(Long.MAX_VALUE).build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-        // Launch activity again.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Then "reset" service to re-enable autofill.
-        disableService();
-        enableService();
-
-        // Try again on activity that disabled it.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-
-        // Try again on other activity.
-        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-    }
-
-    @Test
-    public void testDisableActivity() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .disableAutofill(Long.MAX_VALUE)
-                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
-                .build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-
-        // Launch activity again.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Now try it using a different activity - should work.
-        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDisableActivity() is enough")
-    public void testDisableActivityThenWaitToReenableIt() throws Exception {
-        // Set service.
-        enableService();
-
-        // Need to wait the equivalent of launching 2 activities, plus some extra legging room
-        final long duration = 2 * ACTIVITY_RESURRECTION.ms() + 500;
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .disableAutofill(duration)
-                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
-                .build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-
-        // Launch activity again.
-        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Make sure other app is working.
-        passedTime += launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-
-        // Wait for the timeout, then try again, autofilling it this time.
-        sleep(passedTime, duration);
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDisableActivity() is enough")
-    public void testDisableActivityThenResetServiceToReenableIt() throws Exception {
-        enableService();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .disableAutofill(Long.MAX_VALUE)
-                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
-                .build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-        // Launch activity again.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Make sure other app is working.
-        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-
-        // Then "reset" service to re-enable autofill.
-        disableService();
-        enableService();
-
-        // Try again on activity that disabled it.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-    }
-
-    private void assertAutofillEnabled(AbstractAutoFillActivity activity, boolean expected)
-            throws Exception {
-        ACTIVITY_RESURRECTION.run(
-                "assertAutofillEnabled(" + activity.getComponentName().flattenToShortString() + ")",
-                () -> {
-                    return activity.getAutofillManager().isEnabled() == expected
-                            ? Boolean.TRUE : null;
-                });
-    }
-
-    private void sleep(long passedTime, long disableDuration) {
-        final long napTime = disableDuration - passedTime + 500;
-        if (napTime <= 0) {
-            // Throw an exception so ACTIVITY_RESURRECTION is increased
-            throw new RetryableException("took longer than expcted to launch activities: "
-                            + "passedTime=" + passedTime + "ms, disableDuration=" + disableDuration
-                            + ", ACTIVITY_RESURRECTION=" + ACTIVITY_RESURRECTION
-                            + ", CALLBACK_NOT_CALLED_TIMEOUT_MS=" + CALLBACK_NOT_CALLED_TIMEOUT_MS);
-        }
-        Log.v(TAG, "Sleeping for " + napTime + "ms (duration=" + disableDuration + "ms, passedTime="
-                + passedTime + ")");
-        SystemClock.sleep(napTime);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DismissType.java b/tests/autofillservice/src/android/autofillservice/cts/DismissType.java
deleted file mode 100644
index b2e936c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DismissType.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-/**
- * A simple enum for test cases where the Save UI is dismissed.
- *
- * <p><b>Note:</b> When new values are added to the enum, the equivalent tests must be added to
- * both {@link LoginActivityTest} and {@link SimpleSaveActivityTest}.
- */
-enum DismissType {
-    BACK_BUTTON,
-    HOME_BUTTON,
-    TOUCH_OUTSIDE,
-    FOCUS_OUTSIDE
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DoubleVisitor.java b/tests/autofillservice/src/android/autofillservice/cts/DoubleVisitor.java
deleted file mode 100644
index 8379307..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DoubleVisitor.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import androidx.annotation.NonNull;
-
-/**
- * Implements the Visitor design pattern to visit 2 related objects (like a view and the activity
- * hosting it).
- *
- * @param <V1> 1st visited object
- * @param <V2> 2nd visited object
- */
-// TODO: move to common
-public interface DoubleVisitor<V1, V2> {
-
-    /**
-     * Visit those objects.
-     */
-    void visit(@NonNull V1 visited1, @NonNull V2 visited2);
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DummyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DummyActivity.java
deleted file mode 100644
index a1f5bd9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DummyActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.widget.TextView;
-
-public class DummyActivity extends Activity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        TextView text = new TextView(this);
-        text.setText("foo");
-        setContentView(text);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivity.java
deleted file mode 100644
index 90871ca..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-
-public class DuplicateIdActivity extends AbstractAutoFillActivity {
-    private static final String TAG = "DuplicateIdActivity";
-
-    static final String DUPLICATE_ID = "duplicate_id";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Log.v(TAG, "onCreate(" + savedInstanceState + ")");
-
-        setContentView(R.layout.duplicate_id_layout);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
index 60bac5f..074b422 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
@@ -16,9 +16,9 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.DuplicateIdActivity.DUPLICATE_ID;
-import static android.autofillservice.cts.Helper.assertEqualsIgnoreSession;
+import static android.autofillservice.cts.activities.DuplicateIdActivity.DUPLICATE_ID;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.assertEqualsIgnoreSession;
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
@@ -28,6 +28,12 @@
 
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.DuplicateIdActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/EmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/EmptyActivity.java
deleted file mode 100644
index 87e2b3a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/EmptyActivity.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-
-/**
- * Empty activity
- */
-public class EmptyActivity extends Activity {
-
-    public static final String ID_EMPTY = "empty";
-
-    private View mEmptyView;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.empty);
-        mEmptyView = findViewById(R.id.empty);
-    }
-
-    public View getEmptyView() {
-        return mEmptyView;
-    }
-
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
deleted file mode 100644
index 8a47447..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.findViewByAutofillHint;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-/**
- * An activity containing mostly widgets that should be removed from an auto-fill structure to
- * optimize it.
- */
-public class FatActivity extends AbstractAutoFillActivity {
-
-    static final String ID_CAPTCHA = "captcha";
-    static final String ID_INPUT = "input";
-    static final String ID_INPUT_CONTAINER = "input_container";
-    static final String ID_IMAGE = "image";
-    static final String ID_IMPORTANT_IMAGE = "important_image";
-    static final String ID_ROOT = "root";
-
-    static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS =
-            "not_important_container_excluding_descendants";
-    static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD =
-            "not_important_container_excluding_descendants_child";
-    static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD =
-            "not_important_container_excluding_descendants_grand_child";
-
-    static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS =
-            "important_container_excluding_descendants";
-    static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD =
-            "important_container_excluding_descendants_child";
-    static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD =
-            "important_container_excluding_descendants_grand_child";
-
-    static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS =
-            "not_important_container_mixed_descendants";
-    static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD =
-            "not_important_container_mixed_descendants_child";
-    static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD =
-            "not_important_container_mixed_descendants_grand_child";
-
-    private LinearLayout mRoot;
-    private EditText mCaptcha;
-    private EditText mInput;
-    private ImageView mImage;
-    private ImageView mImportantImage;
-
-    private View mNotImportantContainerExcludingDescendants;
-    private View mNotImportantContainerExcludingDescendantsChild;
-    private View mNotImportantContainerExcludingDescendantsGrandChild;
-
-    private View mImportantContainerExcludingDescendants;
-    private View mImportantContainerExcludingDescendantsChild;
-    private View mImportantContainerExcludingDescendantsGrandChild;
-
-    private View mNotImportantContainerMixedDescendants;
-    private View mNotImportantContainerMixedDescendantsChild;
-    private View mNotImportantContainerMixedDescendantsGrandChild;
-
-    private MyView mViewWithAutofillHints;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.fat_activity);
-
-        mRoot = findViewById(R.id.root);
-        mCaptcha = findViewById(R.id.captcha);
-        mInput = findViewById(R.id.input);
-        mImage = findViewById(R.id.image);
-        mImportantImage = findViewById(R.id.important_image);
-
-        mNotImportantContainerExcludingDescendants = findViewById(
-                R.id.not_important_container_excluding_descendants);
-        mNotImportantContainerExcludingDescendantsChild = findViewById(
-                R.id.not_important_container_excluding_descendants_child);
-        mNotImportantContainerExcludingDescendantsGrandChild = findViewById(
-                R.id.not_important_container_excluding_descendants_grand_child);
-
-        mImportantContainerExcludingDescendants = findViewById(
-                R.id.important_container_excluding_descendants);
-        mImportantContainerExcludingDescendantsChild = findViewById(
-                R.id.important_container_excluding_descendants_child);
-        mImportantContainerExcludingDescendantsGrandChild = findViewById(
-                R.id.important_container_excluding_descendants_grand_child);
-
-        mNotImportantContainerMixedDescendants = findViewById(
-                R.id.not_important_container_mixed_descendants);
-        mNotImportantContainerMixedDescendantsChild = findViewById(
-                R.id.not_important_container_mixed_descendants_child);
-        mNotImportantContainerMixedDescendantsGrandChild = findViewById(
-                R.id.not_important_container_mixed_descendants_grand_child);
-
-        mViewWithAutofillHints = (MyView) findViewByAutofillHint(this, "importantAmI");
-        assertThat(mViewWithAutofillHints).isNotNull();
-
-        // Validation check for importantForAutofill modes
-        assertThat(mRoot.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
-        assertThat(mInput.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
-        assertThat(mCaptcha.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
-        assertThat(mImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
-        assertThat(mImportantImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
-
-        assertThat(mNotImportantContainerExcludingDescendants.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
-        assertThat(mNotImportantContainerExcludingDescendantsChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
-        assertThat(mNotImportantContainerExcludingDescendantsGrandChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
-
-        assertThat(mImportantContainerExcludingDescendants.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
-        assertThat(mImportantContainerExcludingDescendantsChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
-        assertThat(mImportantContainerExcludingDescendantsGrandChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
-
-        assertThat(mNotImportantContainerMixedDescendants.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
-        assertThat(mNotImportantContainerMixedDescendantsChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
-        assertThat(mNotImportantContainerMixedDescendantsGrandChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
-
-        assertThat(mViewWithAutofillHints.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
-        assertThat(mViewWithAutofillHints.isImportantForAutofill()).isTrue();
-    }
-
-    /**
-     * Visits the {@code input} in the UiThread.
-     */
-    void onInput(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> {
-            v.visit(mInput);
-        });
-    }
-
-    /**
-     * Custom view that defines an autofill type so autofill hints are set on {@code ViewNode}.
-     */
-    public static class MyView extends View {
-        public MyView(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-
-        @Override
-        public int getAutofillType() {
-            return AUTOFILL_TYPE_TEXT;
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
index 456f5cf..a24936c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
@@ -15,27 +15,27 @@
  */
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.FatActivity.ID_CAPTCHA;
-import static android.autofillservice.cts.FatActivity.ID_IMAGE;
-import static android.autofillservice.cts.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS;
-import static android.autofillservice.cts.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_IMPORTANT_IMAGE;
-import static android.autofillservice.cts.FatActivity.ID_INPUT;
-import static android.autofillservice.cts.FatActivity.ID_INPUT_CONTAINER;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_ROOT;
-import static android.autofillservice.cts.Helper.findNodeByAutofillHint;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByText;
-import static android.autofillservice.cts.Helper.getNumberNodes;
-import static android.autofillservice.cts.Helper.importantForAutofillAsString;
+import static android.autofillservice.cts.activities.FatActivity.ID_CAPTCHA;
+import static android.autofillservice.cts.activities.FatActivity.ID_IMAGE;
+import static android.autofillservice.cts.activities.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS;
+import static android.autofillservice.cts.activities.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_IMPORTANT_IMAGE;
+import static android.autofillservice.cts.activities.FatActivity.ID_INPUT;
+import static android.autofillservice.cts.activities.FatActivity.ID_INPUT_CONTAINER;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_ROOT;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.findNodeByAutofillHint;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByText;
+import static android.autofillservice.cts.testcore.Helper.getNumberNodes;
+import static android.autofillservice.cts.testcore.Helper.importantForAutofillAsString;
 import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;
 import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
 import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS;
@@ -46,7 +46,10 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.activities.FatActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
 
 import org.junit.Test;
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
deleted file mode 100644
index 99debab..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
+++ /dev/null
@@ -1,856 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.GridActivity.ID_L1C1;
-import static android.autofillservice.cts.GridActivity.ID_L1C2;
-import static android.autofillservice.cts.GridActivity.ID_L2C1;
-import static android.autofillservice.cts.GridActivity.ID_L2C2;
-import static android.autofillservice.cts.Helper.assertFillEventForContextCommitted;
-import static android.autofillservice.cts.Helper.assertFillEventForFieldsClassification;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.provider.Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
-import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_CREDIT_CARD;
-import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EDIT_DISTANCE;
-import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EXACT_MATCH;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.autofillservice.cts.Helper.FieldClassificationResult;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory.Event;
-import android.service.autofill.UserData;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillManager;
-import android.widget.EditText;
-
-import com.android.compatibility.common.util.SettingsStateChangerRule;
-
-import org.junit.ClassRule;
-import org.junit.Test;
-
-import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
-
-@AppModeFull(reason = "Service-specific test")
-public class FieldsClassificationTest extends AbstractGridActivityTestCase {
-
-    @ClassRule
-    public static final SettingsStateChangerRule sFeatureEnabler =
-            new SettingsStateChangerRule(sContext, AUTOFILL_FEATURE_FIELD_CLASSIFICATION, "1");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
-            new SettingsStateChangerRule(sContext,
-                    AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "9");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMinValueChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "4");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxValueChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxCategoryChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "42");
-
-    private AutofillManager mAfm;
-    private final Bundle mLast4Bundle = new Bundle();
-    private final Bundle mCreditCardBundle = new Bundle();
-
-    @Override
-    protected void postActivityLaunched() {
-        mAfm = mActivity.getAutofillManager();
-        mLast4Bundle.putInt("MATCH_SUFFIX", 4);
-
-        mCreditCardBundle.putInt("REQUIRED_ARG_MIN_CC_LENGTH", 13);
-        mCreditCardBundle.putInt("REQUIRED_ARG_MAX_CC_LENGTH", 19);
-        mCreditCardBundle.putInt("OPTIONAL_ARG_SUFFIX_LENGTH", 4);
-    }
-
-    @Test
-    public void testFeatureIsEnabled() throws Exception {
-        enableService();
-        assertThat(mAfm.isFieldClassificationEnabled()).isTrue();
-
-        disableService();
-        assertThat(mAfm.isFieldClassificationEnabled()).isFalse();
-    }
-
-    @Test
-    public void testGetAlgorithm() throws Exception {
-        enableService();
-
-        // Check algorithms
-        final List<String> names = mAfm.getAvailableFieldClassificationAlgorithms();
-        assertThat(names.size()).isAtLeast(1);
-        final String defaultAlgorithm = mAfm.getDefaultFieldClassificationAlgorithm();
-        assertThat(defaultAlgorithm).isNotEmpty();
-        assertThat(names).contains(defaultAlgorithm);
-
-        // Checks invalid service
-        disableService();
-        assertThat(mAfm.getAvailableFieldClassificationAlgorithms()).isEmpty();
-    }
-
-    @Test
-    public void testUserData() throws Exception {
-        assertThat(mAfm.getUserData()).isNull();
-        assertThat(mAfm.getUserDataId()).isNull();
-
-        enableService();
-        mAfm.setUserData(new UserData.Builder("user_data_id", "value", "remote_id")
-                .build());
-        assertThat(mAfm.getUserData()).isNotNull();
-        assertThat(mAfm.getUserDataId()).isEqualTo("user_data_id");
-        final UserData userData = mAfm.getUserData();
-        assertThat(userData.getId()).isEqualTo("user_data_id");
-        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
-        assertThat(userData.getFieldClassificationAlgorithms()).isNull();
-
-        disableService();
-        assertThat(mAfm.getUserData()).isNull();
-        assertThat(mAfm.getUserDataId()).isNull();
-    }
-
-    @Test
-    public void testRequiredAlgorithmsAvailable() throws Exception {
-        enableService();
-        final List<String> availableAlgorithms = mAfm.getAvailableFieldClassificationAlgorithms();
-        assertThat(availableAlgorithms).isNotNull();
-        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EDIT_DISTANCE)).isTrue();
-        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EXACT_MATCH)).isTrue();
-        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_CREDIT_CARD)).isTrue();
-    }
-
-    @Test
-    public void testUserDataConstraints() throws Exception {
-        // NOTE: values set by the SettingsStateChangerRule @Rules should have unique values to
-        // make sure the getters below are reading the right property.
-        assertThat(UserData.getMaxFieldClassificationIdsSize()).isEqualTo(10);
-        assertThat(UserData.getMaxUserDataSize()).isEqualTo(9);
-        assertThat(UserData.getMinValueLength()).isEqualTo(4);
-        assertThat(UserData.getMaxValueLength()).isEqualTo(50);
-        assertThat(UserData.getMaxCategoryCount()).isEqualTo(42);
-    }
-
-    @Test
-    public void testHit_oneUserData_oneDetectableField() throws Exception {
-        simpleHitTest(false, null);
-    }
-
-    @Test
-    public void testHit_invalidAlgorithmIsIgnored() throws Exception {
-        // For simplicity's sake, let's assume that name will never be valid..
-        String invalidName = " ALGORITHM, Y NO INVALID? ";
-
-        simpleHitTest(true, invalidName);
-    }
-
-    @Test
-    public void testHit_userDataAlgorithmIsReset() throws Exception {
-        simpleHitTest(true, null);
-    }
-
-    @Test
-    public void testMiss_exactMatchAlgorithm() throws Exception {
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "t 1234", "cat")
-                .setFieldClassificationAlgorithmForCategory("cat",
-                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "t 5678");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0), null);
-    }
-
-    @Test
-    public void testHit_exactMatchLast4Algorithm() throws Exception {
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "1234", "cat")
-                .setFieldClassificationAlgorithmForCategory("cat",
-                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "T1234");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "cat", 1);
-    }
-
-    @Test
-    public void testHit_CreditCardAlgorithm() throws Exception {
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "1122334455667788", "card")
-                .setFieldClassificationAlgorithmForCategory("card",
-                        REQUIRED_ALGORITHM_CREDIT_CARD, mCreditCardBundle)
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "7788");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "card", 1);
-    }
-
-    @Test
-    public void testHit_useDefaultAlgorithm() throws Exception {
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "1234", "cat")
-                .setFieldClassificationAlgorithm(REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
-                .setFieldClassificationAlgorithmForCategory("dog",
-                        REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "T1234");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "cat", 1);
-    }
-
-    private void simpleHitTest(boolean setAlgorithm, String algorithm) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final UserData.Builder userData = new UserData.Builder("id", "FULLY", "myId");
-        if (setAlgorithm) {
-            userData.setFieldClassificationAlgorithm(algorithm, null);
-        }
-        mAfm.setUserData(userData.build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "myId", 1);
-    }
-
-    @Test
-    public void testHit_sameValueForMultipleCategories() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "FULLY", "cat1")
-                .add("FULLY", "cat2")
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId.get(),
-                                new String[] { "cat1", "cat2"},
-                                new float[] {1, 1})
-                });
-    }
-
-    @Test
-    public void testHit_manyUserData_oneDetectableField_bestMatchIsFirst() throws Exception {
-        manyUserData_oneDetectableField(true);
-    }
-
-    @Test
-    public void testHit_manyUserData_oneDetectableField_bestMatchIsSecond() throws Exception {
-        manyUserData_oneDetectableField(false);
-    }
-
-    private void manyUserData_oneDetectableField(boolean firstMatch) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "Iam1ST", "1stId")
-                .add("Iam2ND", "2ndId").build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, firstMatch ? "IAM111" : "IAM222");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        // Best match is 0.66 (4 of 6), worst is 0.5 (3 of 6)
-        if (firstMatch) {
-            assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
-                    new FieldClassificationResult(fieldId.get(), new String[] { "1stId", "2ndId" },
-                            new float[] { 0.66F, 0.5F })});
-        } else {
-            assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
-                    new FieldClassificationResult(fieldId.get(), new String[] { "2ndId", "1stId" },
-                            new float[] { 0.66F, 0.5F }) });
-        }
-    }
-
-    @Test
-    public void testHit_oneUserData_manyDetectableFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field1 = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
-                .setVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
-                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
-                })
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field1);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully"); // 100%
-        mActivity.setText(1, 2, "fooly"); // 60%
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId1.get(), "myId", 1.0F),
-                        new FieldClassificationResult(fieldId2.get(), "myId", 0.6F),
-                });
-    }
-
-    @Test
-    public void testHit_manyUserData_manyDetectableFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId")
-                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
-                .add("EMPTY", "otherId")
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field1 = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId4 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
-                .setVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
-                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
-                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
-                    fieldId4.set(findAutofillIdByResourceId(context, ID_L2C2));
-                })
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field1);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully"); // u1: 100% u2:  20%
-        mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
-        mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
-        mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId1.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
-                        new FieldClassificationResult(fieldId2.get(),
-                                new String[] { "otherId", "myId" }, new float[] { 1.0F, 0.2F }),
-                        new FieldClassificationResult(fieldId3.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 0.6F, 0.2F }),
-                        new FieldClassificationResult(fieldId4.get(),
-                                new String[] { "otherId", "myId"}, new float[] { 0.80F, 0.2F })});
-    }
-
-    @Test
-    public void testHit_manyUserData_manyDetectableFields_differentClassificationAlgo()
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "1234", "myId")
-                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
-                .add("EMPTY", "otherId")
-                .setFieldClassificationAlgorithmForCategory("myId",
-                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
-                .setFieldClassificationAlgorithmForCategory("otherId",
-                        REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field1 = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
-                .setVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
-                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
-                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
-                })
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field1);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "E1234"); // u1: 100% u2:  20%
-        mActivity.setText(1, 2, "empty"); // u1:   0% u2: 100%
-        mActivity.setText(2, 1, "fULLy"); // u1:   0% u2:  20%
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId1.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
-                        new FieldClassificationResult(fieldId2.get(),
-                                new String[] { "otherId" }, new float[] { 1.0F }),
-                        new FieldClassificationResult(fieldId3.get(),
-                                new String[] { "otherId" }, new float[] { 0.2F })});
-    }
-
-    @Test
-    public void testHit_manyUserDataPerField_manyDetectableFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "zzzzz", "myId") // should not have matched any
-                .add("FULL1", "myId") // match 80%, should not have been reported
-                .add("FULLY", "myId") // match 100%
-                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
-                .add("EMPTY", "otherId")
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field1 = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId4 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
-                .setVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
-                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
-                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
-                    fieldId4.set(findAutofillIdByResourceId(context, ID_L2C2));
-                })
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field1);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully"); // u1: 100% u2:  20%
-        mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
-        mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
-        mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId1.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
-                        new FieldClassificationResult(fieldId2.get(),
-                                new String[] { "otherId", "myId" }, new float[] { 1.0F, 0.2F }),
-                        new FieldClassificationResult(fieldId3.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 0.6F, 0.2F }),
-                        new FieldClassificationResult(fieldId4.get(),
-                                new String[] { "otherId", "myId"}, new float[] { 0.80F, 0.2F })});
-    }
-
-    @Test
-    public void testMiss() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "ABCDEF", "myId").build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "xyz");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForContextCommitted(events.get(0));
-    }
-
-    @Test
-    public void testNoUserInput() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForContextCommitted(events.get(0));
-    }
-
-    @Test
-    public void testHit_usePackageUserData() throws Exception {
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "TEST1", "cat")
-                .setFieldClassificationAlgorithm(null, null)
-                .build());
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId1
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .setUserData(new UserData.Builder("id2", "TEST2", "cat")
-                        .setFieldClassificationAlgorithm(null, null)
-                        .build())
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "test1");
-
-        // Finish context
-        mAfm.commit();
-
-        final Event packageUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
-        assertFillEventForFieldsClassification(packageUserDataEvent, fieldId1.get(), "cat", 0.8F);
-
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setVisitor((contexts, builder) -> fieldId2
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .setFieldClassificationIds(ID_L1C1)
-                .build());
-
-        // Need to switch focus first
-        mActivity.focusCell(1, 2);
-
-        // Trigger second autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final Event defaultUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
-        assertFillEventForFieldsClassification(defaultUserDataEvent, fieldId2.get(), "cat", 1.0F);
-    }
-
-    @Test
-    public void testHit_mergeUserData_manyDetectableFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field1 = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
-                .setVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
-                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
-                })
-                .setUserData(new UserData.Builder("id2", "FOOLY", "otherId")
-                        .add("EMPTY", "myId")
-                        .build())
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field1);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully"); // u1:  20%, u2: 60%
-        mActivity.setText(1, 2, "empty"); // u1: 100%, u2: 20%
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId1.get(),
-                                new String[] { "otherId", "myId" }, new float[] { 0.6F, 0.2F }),
-                        new FieldClassificationResult(fieldId2.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
-                });
-    }
-
-    /*
-     * TODO(b/73648631): other scenarios:
-     *
-     * - Multipartition (for example, one response with FieldsDetection, others with datasets,
-     *   saveinfo, and/or ignoredIds)
-     * - make sure detectable fields don't trigger a new partition
-     * v test partial hit (for example, 'fool' instead of 'full'
-     * v multiple fields
-     * v multiple value
-     * - combinations of above items
-     */
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryCommonTestCase.java
deleted file mode 100644
index 931193f..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryCommonTestCase.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_NUMBER;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.NULL_DATASET_ID;
-import static android.autofillservice.cts.Helper.assertDeprecatedClientState;
-import static android.autofillservice.cts.Helper.assertFillEventForAuthenticationSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetAuthenticationSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetShown;
-import static android.autofillservice.cts.Helper.assertFillEventForSaveShown;
-import static android.autofillservice.cts.Helper.assertNoDeprecatedClientState;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
-import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.inline.InlineFillEventHistoryTest;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.FillEventHistory.Event;
-import android.service.autofill.FillResponse;
-import android.view.View;
-
-import org.junit.Test;
-
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * This is the common test cases with {@link FillEventHistoryTest} and
- * {@link InlineFillEventHistoryTest}.
- */
-@AppModeFull(reason = "Service-specific test")
-public abstract class FillEventHistoryCommonTestCase extends AbstractLoginActivityTestCase {
-
-    protected FillEventHistoryCommonTestCase() {}
-
-    protected FillEventHistoryCommonTestCase(UiBot inlineUiBot) {
-        super(inlineUiBot);
-    }
-
-    protected Bundle getBundle(String key, String value) {
-        final Bundle bundle = new Bundle();
-        bundle.putString(key, value);
-        return bundle;
-    }
-
-    @Test
-    public void testDatasetAuthenticationSelected() throws Exception {
-        enableService();
-
-        // Set up FillResponse with dataset authentication
-        Bundle clientState = new Bundle();
-        clientState.putCharSequence("clientStateKey", "clientStateValue");
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation("Dataset", isInlineMode())
-                        .build());
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setId("name")
-                        .setPresentation("authentication", isInlineMode())
-                        .setAuthentication(authentication)
-                        .build())
-                .setExtras(clientState).build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-
-        // Authenticate
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("authentication");
-        mActivity.assertAutoFilled();
-
-        // Verify fill selection
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-        assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
-        assertFillEventForDatasetAuthenticationSelected(events.get(1), "name",
-                "clientStateKey", "clientStateValue");
-    }
-
-    @Test
-    public void testAuthenticationSelected() throws Exception {
-        enableService();
-
-        // Set up FillResponse with response wide authentication
-        Bundle clientState = new Bundle();
-        clientState.putCharSequence("clientStateKey", "clientStateValue");
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "username")
-                                .setId("name")
-                                .setPresentation("dataset", isInlineMode())
-                                .build())
-                        .setExtras(clientState).build());
-
-        sReplier.addResponse(new CannedFillResponse.Builder().setExtras(clientState)
-                .setPresentation("authentication", isInlineMode())
-                .setAuthentication(authentication, ID_USERNAME)
-                .build());
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-
-        // Authenticate
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("authentication");
-        mUiBot.waitForIdle();
-        mUiBot.selectDataset("dataset");
-        mUiBot.waitForIdle();
-
-        // Verify fill selection
-        final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(4);
-        assertDeprecatedClientState(selection, "clientStateKey", "clientStateValue");
-        List<Event> events = selection.getEvents();
-        assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
-        assertFillEventForAuthenticationSelected(events.get(1), NULL_DATASET_ID,
-                "clientStateKey", "clientStateValue");
-        assertFillEventForDatasetShown(events.get(2), "clientStateKey", "clientStateValue");
-        assertFillEventForDatasetSelected(events.get(3), "name",
-                "clientStateKey", "clientStateValue");
-    }
-
-    @Test
-    public void testDatasetSelected_twoResponses() throws Exception {
-        enableService();
-
-        // Set up first partition with an anonymous dataset
-        Bundle clientState1 = new Bundle();
-        clientState1.putCharSequence("clientStateKey", "Value1");
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation("dataset1", isInlineMode())
-                        .build())
-                .setExtras(clientState1)
-                .build());
-        mActivity.expectAutoFill("username");
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset1");
-        mUiBot.waitForIdle();
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
-            assertDeprecatedClientState(selection, "clientStateKey", "Value1");
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value1");
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID,
-                    "clientStateKey", "Value1");
-        }
-
-        // Set up second partition with a named dataset
-        Bundle clientState2 = new Bundle();
-        clientState2.putCharSequence("clientStateKey", "Value2");
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_PASSWORD, "password2")
-                                .setPresentation("dataset2", isInlineMode())
-                                .setId("name2")
-                                .build())
-                .addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_PASSWORD, "password3")
-                                .setPresentation("dataset3", isInlineMode())
-                                .setId("name3")
-                                .build())
-                .setExtras(clientState2)
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_PASSWORD).build());
-        mActivity.expectPasswordAutoFill("password3");
-
-        // Trigger autofill on password
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset3");
-        mUiBot.waitForIdle();
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
-            assertDeprecatedClientState(selection, "clientStateKey", "Value2");
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
-            assertFillEventForDatasetSelected(events.get(1), "name3",
-                    "clientStateKey", "Value2");
-        }
-
-        mActivity.onPassword((v) -> v.setText("new password"));
-        mActivity.syncRunOnUiThread(() -> mActivity.finish());
-        waitUntilDisconnected();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(4);
-            assertDeprecatedClientState(selection, "clientStateKey", "Value2");
-
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
-            assertFillEventForDatasetSelected(events.get(1), "name3",
-                    "clientStateKey", "Value2");
-            assertFillEventForDatasetShown(events.get(2), "clientStateKey", "Value2");
-            assertFillEventForSaveShown(events.get(3), NULL_DATASET_ID,
-                    "clientStateKey", "Value2");
-        }
-    }
-
-    @Test
-    public void testNoEvents_whenServiceReturnsNullResponse() throws Exception {
-        enableService();
-
-        // First reset
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation("dataset1", isInlineMode())
-                        .build())
-                .build());
-        mActivity.expectAutoFill("username");
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset1");
-        mUiBot.waitForIdleSync();
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
-            assertNoDeprecatedClientState(selection);
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-        }
-
-        // Second request
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.waitForIdleSync();
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasets();
-        waitUntilDisconnected();
-
-        InstrumentedAutoFillService.assertNoFillEventHistory();
-    }
-
-    @Test
-    public void testNoEvents_whenServiceReturnsFailure() throws Exception {
-        enableService();
-
-        // First reset
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation("dataset1", isInlineMode())
-                        .build())
-                .build());
-        mActivity.expectAutoFill("username");
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset1");
-        mUiBot.waitForIdleSync();
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
-            assertNoDeprecatedClientState(selection);
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-        }
-
-        // Second request
-        sReplier.addResponse(new CannedFillResponse.Builder().returnFailure("D'OH!").build());
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.waitForIdleSync();
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasets();
-        waitUntilDisconnected();
-
-        InstrumentedAutoFillService.assertNoFillEventHistory();
-    }
-
-    @Test
-    public void testNoEvents_whenServiceTimesout() throws Exception {
-        enableService();
-
-        // First reset
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation("dataset1", isInlineMode())
-                        .build())
-                .build());
-        mActivity.expectAutoFill("username");
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
-            assertNoDeprecatedClientState(selection);
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-        }
-
-        // Second request
-        sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-        waitUntilDisconnected();
-
-        InstrumentedAutoFillService.assertNoFillEventHistory();
-    }
-
-    /**
-     * Tests the following scenario:
-     *
-     * <ol>
-     *    <li>Activity A is launched.
-     *    <li>Activity A triggers autofill.
-     *    <li>Activity B is launched.
-     *    <li>Activity B triggers autofill.
-     *    <li>User goes back to Activity A.
-     *    <li>Activity A triggers autofill.
-     *    <li>User triggers save on Activity A - at this point, service should have stats of
-     *        activity A.
-     * </ol>
-     */
-    @Test
-    public void testEventsFromPreviousSessionIsDiscarded() throws Exception {
-        enableService();
-
-        // Launch activity A
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setExtras(getBundle("activity", "A"))
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger autofill and IME on activity A.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-
-        // Verify fill selection for Activity A
-        final FillEventHistory selectionA = InstrumentedAutoFillService.getFillEventHistory(0);
-        assertDeprecatedClientState(selectionA, "activity", "A");
-
-        // Launch activity B
-        mActivity.startActivity(new Intent(mActivity, CheckoutActivity.class));
-        mUiBot.assertShownByRelativeId(ID_CC_NUMBER);
-
-        // Trigger autofill on activity B
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setExtras(getBundle("activity", "B"))
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_CC_NUMBER, "4815162342")
-                        .setPresentation("datasetB", isInlineMode())
-                        .build())
-                .build());
-        mUiBot.focusByRelativeId(ID_CC_NUMBER);
-        sReplier.getNextFillRequest();
-
-        // Verify fill selection for Activity B
-        final FillEventHistory selectionB = InstrumentedAutoFillService.getFillEventHistory(1);
-        assertDeprecatedClientState(selectionB, "activity", "B");
-        assertFillEventForDatasetShown(selectionB.getEvents().get(0), "activity", "B");
-
-        // Set response for back to activity A
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setExtras(getBundle("activity", "A"))
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Now switch back to A...
-        mUiBot.pressBack(); // dismiss autofill
-        mUiBot.pressBack(); // dismiss keyboard (or task, if there was no keyboard)
-        final AtomicBoolean focusOnA = new AtomicBoolean();
-        mActivity.syncRunOnUiThread(() -> focusOnA.set(mActivity.hasWindowFocus()));
-        if (!focusOnA.get()) {
-            mUiBot.pressBack(); // dismiss task, if the last pressBack dismissed only the keyboard
-        }
-        mUiBot.assertShownByRelativeId(ID_USERNAME);
-        assertWithMessage("root window has no focus")
-                .that(mActivity.getWindow().getDecorView().hasWindowFocus()).isTrue();
-
-        sReplier.getNextFillRequest();
-
-        // ...and trigger save
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        sReplier.getNextSaveRequest();
-
-        // Finally, make sure history is right
-        final FillEventHistory finalSelection = InstrumentedAutoFillService.getFillEventHistory(1);
-        assertDeprecatedClientState(finalSelection, "activity", "A");
-        assertFillEventForSaveShown(finalSelection.getEvents().get(0), NULL_DATASET_ID, "activity",
-                "A");
-    }
-
-    @Test
-    public void testContextCommitted_withoutFlagOnLastResponse() throws Exception {
-        enableService();
-        // Trigger 1st autofill request
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
-                        .setPresentation("dataset1", isInlineMode())
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill(BACKDOOR_USERNAME);
-        // Trigger autofill and IME on username.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-        // Verify fill history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-        }
-
-        // Trigger 2nd autofill request (which will clear the fill event history)
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_PASSWORD, "whatever")
-                        .setPresentation("dataset2", isInlineMode())
-                        .build())
-                // don't set flags
-                .build());
-        mActivity.expectPasswordAutoFill("whatever");
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset2");
-        mActivity.assertAutoFilled();
-        // Verify fill history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id2");
-        }
-
-        // Finish the context by login in
-        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id2");
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
deleted file mode 100644
index 92e963a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
+++ /dev/null
@@ -1,797 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.NULL_DATASET_ID;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetShown;
-import static android.autofillservice.cts.Helper.assertFillEventForSaveShown;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.FillEventHistory.Event;
-import android.service.autofill.FillResponse;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillId;
-
-import com.google.common.collect.ImmutableMap;
-
-import org.junit.Test;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Test that uses {@link LoginActivity} to test {@link FillEventHistory}.
- */
-@AppModeFull(reason = "Service-specific test")
-public class FillEventHistoryTest extends FillEventHistoryCommonTestCase {
-
-    @Test
-    public void testContextCommitted_whenServiceDidntDoAnything() throws Exception {
-        enableService();
-
-        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert no events where generated
-        InstrumentedAutoFillService.assertNoFillEventHistory();
-    }
-
-    @Test
-    public void textContextCommitted_withoutDatasets() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        sReplier.getNextSaveRequest();
-
-        // Assert it
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForSaveShown(events.get(0), NULL_DATASET_ID);
-    }
-
-
-    @Test
-    public void testContextCommitted_idlessDatasets() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username1")
-                        .setField(ID_PASSWORD, "password1")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username2")
-                        .setField(ID_PASSWORD, "password2")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username1", "password1");
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
-        mUiBot.selectDataset(datasetPicker, "dataset1");
-        mActivity.assertAutoFilled();
-
-        // Verify dataset selection
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-        }
-
-        // Finish the context by login in
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText("USERNAME"));
-
-        final String expectedMessage = getWelcomeMessage("USERNAME");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...and check again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-            assertFillEventForDatasetShown(events.get(2));
-        }
-    }
-
-    @Test
-    public void testContextCommitted_idlessDatasetSelected_datasetWithIdIgnored()
-            throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username1")
-                        .setField(ID_PASSWORD, "password1")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "username2")
-                        .setField(ID_PASSWORD, "password2")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username1", "password1");
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
-        mUiBot.selectDataset(datasetPicker, "dataset1");
-        mActivity.assertAutoFilled();
-
-        // Verify dataset selection
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-        }
-
-        // Finish the context by login in
-        mActivity.onPassword((v) -> v.setText("username1"));
-
-        final String expectedMessage = getWelcomeMessage("username1");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...and check again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-
-            FillEventHistory.Event event2 = events.get(2);
-            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event2.getDatasetId()).isNull();
-            assertThat(event2.getClientState()).isNull();
-            assertThat(event2.getSelectedDatasetIds()).isEmpty();
-            assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
-            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
-                    ID_PASSWORD);
-            final Map<AutofillId, String> changedFields = event2.getChangedFields();
-            assertThat(changedFields).containsExactly(passwordId, "id2");
-            assertThat(event2.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    @Test
-    public void testContextCommitted_idlessDatasetIgnored_datasetWithIdSelected()
-            throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username1")
-                        .setField(ID_PASSWORD, "password1")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "username2")
-                        .setField(ID_PASSWORD, "password2")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username2", "password2");
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
-        mUiBot.selectDataset(datasetPicker, "dataset2");
-        mActivity.assertAutoFilled();
-
-        // Verify dataset selection
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id2");
-        }
-
-        // Finish the context by login in
-        mActivity.onPassword((v) -> v.setText("username2"));
-
-        final String expectedMessage = getWelcomeMessage("username2");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...and check again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id2");
-
-            final FillEventHistory.Event event2 = events.get(2);
-            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event2.getDatasetId()).isNull();
-            assertThat(event2.getClientState()).isNull();
-            assertThat(event2.getSelectedDatasetIds()).containsExactly("id2");
-            assertThat(event2.getIgnoredDatasetIds()).isEmpty();
-            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
-                    ID_PASSWORD);
-            final Map<AutofillId, String> changedFields = event2.getChangedFields();
-            assertThat(changedFields).containsExactly(passwordId, "id2");
-            assertThat(event2.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, no dataset was selected by the user,
-     * neither the user entered values that were present in these datasets.
-     */
-    @Test
-    public void testContextCommitted_noDatasetSelected_valuesNotManuallyEntered() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, "username1")
-                        .setField(ID_PASSWORD, "password1")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "username2")
-                        .setField(ID_PASSWORD, "password2")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("dataset1", "dataset2");
-
-        // Verify history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetShown(events.get(0));
-        }
-        // Enter values not present at the datasets
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText("USERNAME"));
-
-        // Finish the context by login in
-        final String expectedMessage = getWelcomeMessage("USERNAME");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Verify history again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            final Event event = events.get(1);
-            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event.getDatasetId()).isNull();
-            assertThat(event.getClientState()).isNull();
-            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
-            assertThat(event.getChangedFields()).isEmpty();
-            assertThat(event.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, just one dataset was selected by the user,
-     * and the user changed the values provided by the service.
-     */
-    @Test
-    public void testContextCommitted_oneDatasetSelected() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, "username1")
-                        .setField(ID_PASSWORD, "password1")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "username2")
-                        .setField(ID_PASSWORD, "password2")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username1", "password1");
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
-        mUiBot.selectDataset(datasetPicker, "dataset1");
-        mActivity.assertAutoFilled();
-
-        // Verify dataset selection
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-        }
-
-        // Finish the context by login in
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText("USERNAME"));
-
-        final String expectedMessage = getWelcomeMessage("USERNAME");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...and check again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-
-            assertFillEventForDatasetShown(events.get(2));
-            final FillEventHistory.Event event2 = events.get(3);
-            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event2.getDatasetId()).isNull();
-            assertThat(event2.getClientState()).isNull();
-            assertThat(event2.getSelectedDatasetIds()).containsExactly("id1");
-            assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
-            final Map<AutofillId, String> changedFields = event2.getChangedFields();
-            final FillContext context = request.contexts.get(0);
-            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
-            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-
-            assertThat(changedFields).containsExactlyEntriesIn(
-                    ImmutableMap.of(usernameId, "id1", passwordId, "id1"));
-            assertThat(event2.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, both datasets were selected by the user,
-     * and the user changed the values provided by the service.
-     */
-    @Test
-    public void testContextCommitted_multipleDatasetsSelected() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_PASSWORD, "password")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username");
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-
-        // Autofill username
-        mUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-        }
-
-        // Autofill password
-        mActivity.expectPasswordAutoFill("password");
-
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.selectDataset("dataset2");
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
-
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-            assertFillEventForDatasetShown(events.get(2));
-            assertFillEventForDatasetSelected(events.get(3), "id2");
-        }
-
-        // Finish the context by login in
-        mActivity.onPassword((v) -> v.setText("username"));
-
-        final String expectedMessage = getWelcomeMessage("username");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(6);
-
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-            assertFillEventForDatasetShown(events.get(2));
-            assertFillEventForDatasetSelected(events.get(3), "id2");
-
-            assertFillEventForDatasetShown(events.get(4));
-            final FillEventHistory.Event event3 = events.get(5);
-            assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event3.getDatasetId()).isNull();
-            assertThat(event3.getClientState()).isNull();
-            assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
-            assertThat(event3.getIgnoredDatasetIds()).isEmpty();
-            final Map<AutofillId, String> changedFields = event3.getChangedFields();
-            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
-                    ID_PASSWORD);
-            assertThat(changedFields).containsExactly(passwordId, "id2");
-            assertThat(event3.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, both datasets were selected by the user,
-     * and the user didn't change the values provided by the service.
-     */
-    @Test
-    public void testContextCommitted_multipleDatasetsSelected_butNotChanged() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_PASSWORD, "whatever")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill(BACKDOOR_USERNAME);
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Autofill username
-        mUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-        }
-
-        // Autofill password
-        mActivity.expectPasswordAutoFill("whatever");
-
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.selectDataset("dataset2");
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
-
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-            assertFillEventForDatasetShown(events.get(2));
-            assertFillEventForDatasetSelected(events.get(3), "id2");
-        }
-
-        // Finish the context by login in
-        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(5);
-
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-            assertFillEventForDatasetShown(events.get(2));
-            assertFillEventForDatasetSelected(events.get(3), "id2");
-
-            final FillEventHistory.Event event3 = events.get(4);
-            assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event3.getDatasetId()).isNull();
-            assertThat(event3.getClientState()).isNull();
-            assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
-            assertThat(event3.getIgnoredDatasetIds()).isEmpty();
-            assertThat(event3.getChangedFields()).isEmpty();
-            assertThat(event3.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, the user selected the dataset, than changed
-     * the autofilled values, but then change the values again so they match what was provided by
-     * the service.
-     */
-    @Test
-    public void testContextCommitted_oneDatasetSelected_Changed_thenChangedBack()
-            throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, "username")
-                        .setField(ID_PASSWORD, "username")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username", "username");
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        mUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-
-        // Verify dataset selection
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-        }
-
-        // Change the fields to different values from0 datasets
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText("USERNAME"));
-
-        // Then change back to dataset values
-        mActivity.onUsername((v) -> v.setText("username"));
-        mActivity.onPassword((v) -> v.setText("username"));
-
-        final String expectedMessage = getWelcomeMessage("username");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...and check again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-            assertFillEventForDatasetShown(events.get(2));
-
-            FillEventHistory.Event event4 = events.get(3);
-            assertThat(event4.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event4.getDatasetId()).isNull();
-            assertThat(event4.getClientState()).isNull();
-            assertThat(event4.getSelectedDatasetIds()).containsExactly("id1");
-            assertThat(event4.getIgnoredDatasetIds()).isEmpty();
-            assertThat(event4.getChangedFields()).isEmpty();
-            assertThat(event4.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, the user did not selected any dataset, but
-     * the user manually entered values that match what was provided by the service.
-     */
-    @Test
-    public void testContextCommitted_noDatasetSelected_butManuallyEntered()
-            throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
-                        .setField(ID_PASSWORD, "NotUsedPassword")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "NotUserUsername")
-                        .setField(ID_PASSWORD, "whatever")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("dataset1", "dataset2");
-
-        // Verify history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetShown(events.get(0));
-        }
-
-        // Enter values present at the datasets
-        mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
-        mActivity.onPassword((v) -> v.setText("whatever"));
-
-        // Finish the context by login in
-        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Verify history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            FillEventHistory.Event event = events.get(1);
-            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event.getDatasetId()).isNull();
-            assertThat(event.getClientState()).isNull();
-            assertThat(event.getSelectedDatasetIds()).isEmpty();
-            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
-            assertThat(event.getChangedFields()).isEmpty();
-            final FillContext context = request.contexts.get(0);
-            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
-            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-
-            final Map<AutofillId, Set<String>> manuallyEnteredFields =
-                    event.getManuallyEnteredField();
-            assertThat(manuallyEnteredFields).isNotNull();
-            assertThat(manuallyEnteredFields.size()).isEqualTo(2);
-            assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1");
-            assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2");
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, the user did not selected any dataset, but
-     * the user manually entered values that match what was provided by the service on different
-     * datasets.
-     */
-    @Test
-    public void testContextCommitted_noDatasetSelected_butManuallyEntered_matchingMultipleDatasets()
-            throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
-                        .setField(ID_PASSWORD, "NotUsedPassword")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "NotUserUsername")
-                        .setField(ID_PASSWORD, "whatever")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id3")
-                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
-                        .setField(ID_PASSWORD, "whatever")
-                        .setPresentation(createPresentation("dataset3"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("dataset1", "dataset2", "dataset3");
-
-        // Verify history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetShown(events.get(0));
-        }
-
-        // Enter values present at the datasets
-        mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
-        mActivity.onPassword((v) -> v.setText("whatever"));
-
-        // Finish the context by login in
-        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Verify history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-
-            final FillEventHistory.Event event = events.get(1);
-            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event.getDatasetId()).isNull();
-            assertThat(event.getClientState()).isNull();
-            assertThat(event.getSelectedDatasetIds()).isEmpty();
-            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2", "id3");
-            assertThat(event.getChangedFields()).isEmpty();
-            final FillContext context = request.contexts.get(0);
-            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
-            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-
-            final Map<AutofillId, Set<String>> manuallyEnteredFields =
-                    event.getManuallyEnteredField();
-            assertThat(manuallyEnteredFields.size()).isEqualTo(2);
-            assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1", "id3");
-            assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2", "id3");
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
deleted file mode 100644
index 407fa30..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.service.autofill.FillResponse.FLAG_DISABLE_ACTIVITY_ONLY;
-import static android.service.autofill.FillResponse.FLAG_TRACK_CONTEXT_COMMITED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
-import android.service.autofill.SaveInfo;
-import android.service.autofill.UserData;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.RemoteViews;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class FillResponseTest {
-
-    private final AutofillId mAutofillId = new AutofillId(42);
-    private final FillResponse.Builder mBuilder = new FillResponse.Builder();
-    private final AutofillId[] mIds = new AutofillId[] { mAutofillId };
-    private final SaveInfo mSaveInfo = new SaveInfo.Builder(0, mIds).build();
-    private final Bundle mClientState = new Bundle();
-    private final Dataset mDataset = new Dataset.Builder()
-            .setValue(mAutofillId, AutofillValue.forText("forty-two"))
-            .build();
-    private final long mDisableDuration = 666;
-    @Mock private RemoteViews mPresentation;
-    @Mock private RemoteViews mHeader;
-    @Mock private RemoteViews mFooter;
-    @Mock private IntentSender mIntentSender;
-    private final UserData mUserData = new UserData.Builder("id", "value", "cat").build();
-
-    @Test
-    public void testBuilder_setAuthentication_invalid() {
-        // null ids
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setAuthentication(null, mIntentSender, mPresentation));
-        // empty ids
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setAuthentication(new AutofillId[] {}, mIntentSender,
-                        mPresentation));
-        // ids with null value
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setAuthentication(new AutofillId[] {null}, mIntentSender,
-                        mPresentation));
-        // null intent sender
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setAuthentication(mIds, null, mPresentation));
-        // null presentation
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setAuthentication(mIds, mIntentSender, null));
-    }
-
-    @Test
-    public void testBuilder_setAuthentication_valid() {
-        new FillResponse.Builder().setAuthentication(mIds, null, null);
-        new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
-    }
-
-    @Test
-    public void testBuilder_setAuthentication_illegalState() {
-        assertThrows(IllegalStateException.class,
-                () -> new FillResponse.Builder().setHeader(mHeader).setAuthentication(mIds,
-                        mIntentSender, mPresentation));
-        assertThrows(IllegalStateException.class,
-                () -> new FillResponse.Builder().setFooter(mFooter).setAuthentication(mIds,
-                        mIntentSender, mPresentation));
-    }
-
-    @Test
-    public void testBuilder_setHeaderOrFooterInvalid() {
-        assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setHeader(null));
-        assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setFooter(null));
-    }
-
-    @Test
-    public void testBuilder_setHeaderOrFooterAfterAuthentication() {
-        FillResponse.Builder builder =
-                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
-        assertThrows(IllegalStateException.class, () -> builder.setHeader(mHeader));
-        assertThrows(IllegalStateException.class, () -> builder.setHeader(mFooter));
-    }
-
-    @Test
-    public void testBuilder_setUserDataInvalid() {
-        assertThrows(NullPointerException.class, () -> new FillResponse.Builder()
-                .setUserData(null));
-    }
-
-    @Test
-    public void testBuilder_setUserDataAfterAuthentication() {
-        FillResponse.Builder builder =
-                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
-        assertThrows(IllegalStateException.class, () -> builder.setUserData(mUserData));
-    }
-
-    @Test
-    public void testBuilder_setFlag_invalid() {
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.setFlags(-1));
-    }
-
-    @Test
-    public void testBuilder_setFlag_valid() {
-        mBuilder.setFlags(0);
-        mBuilder.setFlags(FLAG_TRACK_CONTEXT_COMMITED);
-        mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY);
-    }
-
-    @Test
-    public void testBuilder_disableAutofill_invalid() {
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(0));
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(-1));
-    }
-
-    @Test
-    public void testBuilder_disableAutofill_valid() {
-        mBuilder.disableAutofill(mDisableDuration);
-        mBuilder.disableAutofill(Long.MAX_VALUE);
-    }
-
-    @Test
-    public void testBuilder_disableAutofill_mustBeTheOnlyMethodCalled() {
-        // No method can be called after disableAutofill()
-        mBuilder.disableAutofill(mDisableDuration);
-        assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
-        assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(mDataset));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setFieldClassificationIds(mAutofillId));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setClientState(mClientState));
-
-        // And vice-versa...
-        final FillResponse.Builder builder1 = new FillResponse.Builder().setSaveInfo(mSaveInfo);
-        assertThrows(IllegalStateException.class, () -> builder1.disableAutofill(mDisableDuration));
-        final FillResponse.Builder builder2 = new FillResponse.Builder().addDataset(mDataset);
-        assertThrows(IllegalStateException.class, () -> builder2.disableAutofill(mDisableDuration));
-        final FillResponse.Builder builder3 =
-                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
-        assertThrows(IllegalStateException.class, () -> builder3.disableAutofill(mDisableDuration));
-        final FillResponse.Builder builder4 =
-                new FillResponse.Builder().setFieldClassificationIds(mAutofillId);
-        assertThrows(IllegalStateException.class, () -> builder4.disableAutofill(mDisableDuration));
-        final FillResponse.Builder builder5 =
-                new FillResponse.Builder().setClientState(mClientState);
-        assertThrows(IllegalStateException.class, () -> builder5.disableAutofill(mDisableDuration));
-    }
-
-    @Test
-    public void testBuilder_setFieldClassificationIds_invalid() {
-        assertThrows(NullPointerException.class,
-                () -> mBuilder.setFieldClassificationIds((AutofillId) null));
-        assertThrows(NullPointerException.class,
-                () -> mBuilder.setFieldClassificationIds((AutofillId[]) null));
-        final AutofillId[] oneTooMany =
-                new AutofillId[UserData.getMaxFieldClassificationIdsSize() + 1];
-        for (int i = 0; i < oneTooMany.length; i++) {
-            oneTooMany[i] = new AutofillId(i);
-        }
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setFieldClassificationIds(oneTooMany));
-    }
-
-    @Test
-    public void testBuilder_setFieldClassificationIds_valid() {
-        mBuilder.setFieldClassificationIds(mAutofillId);
-    }
-
-    @Test
-    public void testBuilder_setFieldClassificationIds_setsFlag() {
-        mBuilder.setFieldClassificationIds(mAutofillId);
-        assertThat(mBuilder.build().getFlags()).isEqualTo(FLAG_TRACK_CONTEXT_COMMITED);
-    }
-
-    @Test
-    public void testBuilder_setFieldClassificationIds_addsFlag() {
-        mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY).setFieldClassificationIds(mAutofillId);
-        assertThat(mBuilder.build().getFlags())
-                .isEqualTo(FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
-    }
-
-    @Test
-    public void testBuild_invalid() {
-        assertThrows(IllegalStateException.class, () -> mBuilder.build());
-    }
-
-    @Test
-    public void testBuild_valid() {
-        // authentication only
-        assertThat(new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation)
-                .build()).isNotNull();
-        // save info only
-        assertThat(new FillResponse.Builder().setSaveInfo(mSaveInfo).build()).isNotNull();
-        // dataset only
-        assertThat(new FillResponse.Builder().addDataset(mDataset).build()).isNotNull();
-        // disable autofill only
-        assertThat(new FillResponse.Builder().disableAutofill(mDisableDuration).build())
-                .isNotNull();
-        // fill detection only
-        assertThat(new FillResponse.Builder().setFieldClassificationIds(mAutofillId).build())
-                .isNotNull();
-        // client state only
-        assertThat(new FillResponse.Builder().setClientState(mClientState).build())
-                .isNotNull();
-    }
-
-    @Test
-    public void testBuilder_build_headerOrFooterWithoutDatasets() {
-        assertThrows(IllegalStateException.class,
-                () -> new FillResponse.Builder().setHeader(mHeader).build());
-        assertThrows(IllegalStateException.class,
-                () -> new FillResponse.Builder().setFooter(mFooter).build());
-    }
-
-    @Test
-    public void testNoMoreInteractionsAfterBuild() {
-        assertThat(mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build())
-                .isNotNull();
-
-        assertThrows(IllegalStateException.class, () -> mBuilder.build());
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build());
-        assertThrows(IllegalStateException.class, () -> mBuilder.setIgnoredIds(mIds));
-        assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(null));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setClientState(mClientState));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setFlags(0));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setFieldClassificationIds(mAutofillId));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setHeader(mHeader));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setFooter(mFooter));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setUserData(mUserData));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setPresentationCancelIds(null));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
deleted file mode 100644
index b95fec6..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import android.widget.FrameLayout;
-
-import androidx.annotation.Nullable;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity containing an fragment
- */
-public class FragmentContainerActivity extends AbstractAutoFillActivity {
-    static final String FRAGMENT_TAG =
-            FragmentContainerActivity.class.getName() + "#FRAGMENT_TAG";
-    private CountDownLatch mResumed = new CountDownLatch(1);
-    private CountDownLatch mStopped = new CountDownLatch(0);
-    private FrameLayout mRootContainer;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.fragment_container);
-
-        mRootContainer = findViewById(R.id.rootContainer);
-
-        // have to manually add fragment as we cannot remove it otherwise
-        getFragmentManager().beginTransaction().add(R.id.rootContainer,
-                new FragmentWithEditText(), FRAGMENT_TAG).commitNow();
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-
-        mStopped = new CountDownLatch(1);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        mResumed.countDown();
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-
-        mResumed = new CountDownLatch(1);
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        mStopped.countDown();
-    }
-
-    /**
-     * Sets whether the root container is focusable or not.
-     *
-     * <p>It's initially set as {@code trye} in the XML layout so autofill is not automatically
-     * triggered in the edit text before the service is prepared to handle it.
-     */
-    public void setRootContainerFocusable(boolean focusable) {
-        mRootContainer.setFocusable(focusable);
-        mRootContainer.setFocusableInTouchMode(focusable);
-    }
-
-    public boolean waitUntilResumed() throws InterruptedException {
-        return mResumed.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-    }
-
-    public boolean waitUntilStopped() throws InterruptedException {
-        return mStopped.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FragmentWithEditText.java b/tests/autofillservice/src/android/autofillservice/cts/FragmentWithEditText.java
deleted file mode 100644
index 52fd39a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FragmentWithEditText.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-
-/**
- * A fragment with containing {@link EditText}s
- */
-public class FragmentWithEditText extends Fragment {
-    @Override
-    @Nullable public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
-            Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.fragment_with_edittext, null);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FragmentWithMoreEditTexts.java b/tests/autofillservice/src/android/autofillservice/cts/FragmentWithMoreEditTexts.java
deleted file mode 100644
index e2e16ba..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FragmentWithMoreEditTexts.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-
-/**
- * A fragment with containing more {@link EditText}s
- */
-public class FragmentWithMoreEditTexts extends Fragment {
-    @Override
-    @Nullable public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
-            Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.fragment_with_more_edittexts, null);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java b/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
deleted file mode 100644
index ac60ebc..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.GridLayout;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.ArrayList;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity that contains a 4x4 grid of cells (named {@code l1c1} to {@code l4c2}) plus
- * {@code save} and {@code clear} buttons.
- */
-public class GridActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "GridActivity";
-    private static final int N_ROWS = 4;
-    private static final int N_COLS = 2;
-
-    public static final String ID_L1C1 = getResourceId(1, 1);
-    public static final String ID_L1C2 = getResourceId(1, 2);
-    public static final String ID_L2C1 = getResourceId(2, 1);
-    public static final String ID_L2C2 = getResourceId(2, 2);
-    public static final String ID_L3C1 = getResourceId(3, 1);
-    public static final String ID_L3C2 = getResourceId(3, 2);
-    public static final String ID_L4C1 = getResourceId(4, 1);
-    public static final String ID_L4C2 = getResourceId(4, 2);
-
-    private GridLayout mGrid;
-    private final EditText[][] mCells = new EditText[4][2];
-    private Button mSaveButton;
-    private Button mClearButton;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.grid_activity);
-
-        mGrid = findViewById(R.id.grid);
-        mCells[0][0] = findViewById(R.id.l1c1);
-        mCells[0][1] = findViewById(R.id.l1c2);
-        mCells[1][0] = findViewById(R.id.l2c1);
-        mCells[1][1] = findViewById(R.id.l2c2);
-        mCells[2][0] = findViewById(R.id.l3c1);
-        mCells[2][1] = findViewById(R.id.l3c2);
-        mCells[3][0] = findViewById(R.id.l4c1);
-        mCells[3][1] = findViewById(R.id.l4c2);
-        mSaveButton = findViewById(R.id.save);
-        mClearButton = findViewById(R.id.clear);
-
-        mSaveButton.setOnClickListener((v) -> save());
-        mClearButton.setOnClickListener((v) -> resetFields());
-    }
-
-    void save() {
-        getSystemService(AutofillManager.class).commit();
-    }
-
-    void resetFields() {
-        for (int i = 0; i < N_ROWS; i++) {
-            for (int j = 0; j < N_COLS; j++) {
-                mCells[i][j].setText("");
-            }
-        }
-        getSystemService(AutofillManager.class).cancel();
-    }
-
-    EditText getCell(int row, int column) {
-        return mCells[row - 1][column - 1];
-    }
-
-    public static String getResourceId(int line, int col) {
-        return "l" + line + "c" + col;
-    }
-
-    public void onCell(int row, int column, Visitor<EditText> v) {
-        final EditText cell = getCell(row, column);
-        syncRunOnUiThread(() -> v.visit(cell));
-    }
-
-    public void focusCell(int row, int column) {
-        onCell(row, column, EditText::requestFocus);
-    }
-
-    public void clearCell(int row, int column) {
-        onCell(row, column, (c) -> c.setText(""));
-    }
-
-    public void setText(int row, int column, String text) {
-        onCell(row, column, (c) -> c.setText(text));
-    }
-
-    public void forceAutofill(int row, int column) {
-        onCell(row, column, (c) -> getAutofillManager().requestAutofill(c));
-    }
-
-    public void removeCell(int row, int column) {
-        onCell(row, column, (c) -> mGrid.removeView(c));
-    }
-
-    public void addCell(int row, int column, EditText cell) {
-        mCells[row - 1][column - 1] = cell;
-        // TODO: ideally it should be added in the right place...
-        syncRunOnUiThread(() -> mGrid.addView(cell));
-    }
-
-    public void triggerAutofill(boolean manually, int row, int column) {
-        if (manually) {
-            forceAutofill(row, column);
-        } else {
-            focusCell(row, column);
-        }
-    }
-
-    public String getText(int row, int column) throws InterruptedException {
-        final long timeoutMs = 100;
-        final BlockingQueue<String> queue = new LinkedBlockingQueue<>(1);
-        onCell(row, column, (c) -> Helper.offer(queue, c.getText().toString(), timeoutMs));
-        final String text = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
-        if (text == null) {
-            throw new RetryableException("text not set in " + timeoutMs + "ms");
-        }
-        return text;
-    }
-
-    public FillExpectation expectAutofill() {
-        return new FillExpectation();
-    }
-
-    public void dumpCells() {
-        final StringBuilder output = new StringBuilder("dumpCells():\n");
-        for (int i = 0; i < N_ROWS; i++) {
-            for (int j = 0; j < N_COLS; j++) {
-                final String id = getResourceId(i + 1, j + 1);
-                final String value = mCells[i][j].getText().toString();
-                output.append('\t').append(id).append("='").append(value).append("'\n");
-            }
-        }
-        Log.d(TAG, output.toString());
-    }
-
-    final class FillExpectation {
-
-        private final ArrayList<OneTimeTextWatcher> mWatchers = new ArrayList<>();
-
-        public FillExpectation onCell(int line, int col, String value) {
-            final String resourceId = getResourceId(line, col);
-            final EditText cell = getCell(line, col);
-            final OneTimeTextWatcher watcher = new OneTimeTextWatcher(resourceId, cell, value);
-            mWatchers.add(watcher);
-            cell.addTextChangedListener(watcher);
-            return this;
-        }
-
-        public void assertAutoFilled() throws Exception {
-            try {
-                for (int i = 0; i < mWatchers.size(); i++) {
-                    final OneTimeTextWatcher watcher = mWatchers.get(i);
-                    watcher.assertAutoFilled();
-                }
-            } catch (AssertionError | Exception e) {
-                dumpCells();
-                throw e;
-            }
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
deleted file mode 100644
index d71006e..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ /dev/null
@@ -1,1596 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.UiBot.PORTRAIT;
-import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
-import static android.provider.Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE;
-import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
-import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_DATASETS_SHOWN;
-import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.app.assist.AssistStructure.WindowNode;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.icu.util.Calendar;
-import android.os.Bundle;
-import android.os.Environment;
-import android.provider.Settings;
-import android.service.autofill.FieldClassification;
-import android.service.autofill.FieldClassification.Match;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.InlinePresentation;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Size;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStructure.HtmlInfo;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillManager;
-import android.view.autofill.AutofillManager.AutofillCallback;
-import android.view.autofill.AutofillValue;
-import android.webkit.WebView;
-import android.widget.RemoteViews;
-import android.widget.inline.InlinePresentationSpec;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.autofill.inline.v1.InlineSuggestionUi;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.BitmapUtils;
-import com.android.compatibility.common.util.OneTimeSettingsListener;
-import com.android.compatibility.common.util.SettingsUtils;
-import com.android.compatibility.common.util.ShellUtils;
-import com.android.compatibility.common.util.TestNameUtils;
-import com.android.compatibility.common.util.Timeout;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-import java.util.regex.Pattern;
-
-/**
- * Helper for common funcionalities.
- */
-public final class Helper {
-
-    public static final String TAG = "AutoFillCtsHelper";
-
-    public static final boolean VERBOSE = false;
-
-    public static final String MY_PACKAGE = "android.autofillservice.cts";
-
-    public static final String ID_USERNAME_LABEL = "username_label";
-    public static final String ID_USERNAME = "username";
-    public static final String ID_PASSWORD_LABEL = "password_label";
-    public static final String ID_PASSWORD = "password";
-    public static final String ID_LOGIN = "login";
-    public static final String ID_OUTPUT = "output";
-    public static final String ID_STATIC_TEXT = "static_text";
-    public static final String ID_EMPTY = "empty";
-    public static final String ID_CANCEL_FILL = "cancel_fill";
-
-    public static final String NULL_DATASET_ID = null;
-
-    public static final char LARGE_STRING_CHAR = '6';
-    // NOTE: cannot be much large as it could ANR and fail the test.
-    public static final int LARGE_STRING_SIZE = 100_000;
-    public static final String LARGE_STRING = com.android.compatibility.common.util.TextUtils
-            .repeat(LARGE_STRING_CHAR, LARGE_STRING_SIZE);
-
-    /**
-     * Can be used in cases where the autofill values is required by irrelevant (like adding a
-     * value to an authenticated dataset).
-     */
-    public static final String UNUSED_AUTOFILL_VALUE = null;
-
-    private static final String ACCELLEROMETER_CHANGE =
-            "content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
-                    + "--bind value:i:%d";
-
-    private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory()
-            + "/CtsAutoFillServiceTestCases";
-
-    private static final Timeout SETTINGS_BASED_SHELL_CMD_TIMEOUT = new Timeout(
-            "SETTINGS_SHELL_CMD_TIMEOUT", OneTimeSettingsListener.DEFAULT_TIMEOUT_MS / 2, 2,
-            OneTimeSettingsListener.DEFAULT_TIMEOUT_MS);
-
-    /**
-     * Helper interface used to filter nodes.
-     *
-     * @param <T> node type
-     */
-    interface NodeFilter<T> {
-        /**
-         * Returns whether the node passes the filter for such given id.
-         */
-        boolean matches(T node, Object id);
-    }
-
-    private static final NodeFilter<ViewNode> RESOURCE_ID_FILTER = (node, id) -> {
-        return id.equals(node.getIdEntry());
-    };
-
-    private static final NodeFilter<ViewNode> HTML_NAME_FILTER = (node, id) -> {
-        return id.equals(getHtmlName(node));
-    };
-
-    private static final NodeFilter<ViewNode> HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> {
-        return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry());
-    };
-
-    private static final NodeFilter<ViewNode> TEXT_FILTER = (node, id) -> {
-        return id.equals(node.getText());
-    };
-
-    private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> {
-        return hasHint(node.getAutofillHints(), id);
-    };
-
-    private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> {
-        final String className = node.getClassName();
-        if (!className.equals("android.webkit.WebView")) return false;
-
-        final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form");
-        final String formName = getAttributeValue(htmlInfo, "name");
-        return id.equals(formName);
-    };
-
-    private static final NodeFilter<View> AUTOFILL_HINT_VIEW_FILTER = (view, id) -> {
-        return hasHint(view.getAutofillHints(), id);
-    };
-
-    private static String toString(AssistStructure structure, StringBuilder builder) {
-        builder.append("[component=").append(structure.getActivityComponent());
-        final int nodes = structure.getWindowNodeCount();
-        for (int i = 0; i < nodes; i++) {
-            final WindowNode windowNode = structure.getWindowNodeAt(i);
-            dump(builder, windowNode.getRootViewNode(), " ", 0);
-        }
-        return builder.append(']').toString();
-    }
-
-    @NonNull
-    public static String toString(@NonNull AssistStructure structure) {
-        return toString(structure, new StringBuilder());
-    }
-
-    @Nullable
-    public static String toString(@Nullable AutofillValue value) {
-        if (value == null) return null;
-        if (value.isText()) {
-            // We don't care about PII...
-            final CharSequence text = value.getTextValue();
-            return text == null ? null : text.toString();
-        }
-        return value.toString();
-    }
-
-    /**
-     * Dump the assist structure on logcat.
-     */
-    public static void dumpStructure(String message, AssistStructure structure) {
-        Log.i(TAG, toString(structure, new StringBuilder(message)));
-    }
-
-    /**
-     * Dump the contexts on logcat.
-     */
-    public static void dumpStructure(String message, List<FillContext> contexts) {
-        for (FillContext context : contexts) {
-            dumpStructure(message, context.getStructure());
-        }
-    }
-
-    /**
-     * Dumps the state of the autofill service on logcat.
-     */
-    public static void dumpAutofillService(@NonNull String tag) {
-        final String autofillDump = runShellCommand("dumpsys autofill");
-        Log.i(tag, "dumpsys autofill\n\n" + autofillDump);
-        final String myServiceDump = runShellCommand("dumpsys activity service %s",
-                InstrumentedAutoFillService.SERVICE_NAME);
-        Log.i(tag, "my service dump: \n" + myServiceDump);
-    }
-
-    /**
-     * Dumps the state of {@link android.service.autofill.InlineSuggestionRenderService}, and assert
-     * that it says the number of active inline suggestion views is the given number.
-     *
-     * <p>Note that ideally we should have a test api to fetch the number and verify against it.
-     * But at the time this test is added for Android 11, we have passed the deadline for adding
-     * the new test api, hence this approach.
-     */
-    public static void assertActiveViewCountFromInlineSuggestionRenderService(int count) {
-        String response = runShellCommand(
-                "dumpsys activity service .InlineSuggestionRenderService");
-        Log.d(TAG, "InlineSuggestionRenderService dump: " + response);
-        Pattern pattern = Pattern.compile(".*mActiveInlineSuggestions: " + count + ".*");
-        assertWithMessage("Expecting view count " + count
-                + ", but seeing different count from service dumpsys " + response).that(
-                pattern.matcher(response).find()).isTrue();
-    }
-
-    /**
-     * Sets whether the user completed the initial setup.
-     */
-    public static void setUserComplete(Context context, boolean complete) {
-        SettingsUtils.syncSet(context, USER_SETUP_COMPLETE, complete ? "1" : null);
-    }
-
-    private static void dump(@NonNull StringBuilder builder, @NonNull ViewNode node,
-            @NonNull String prefix, int childId) {
-        final int childrenSize = node.getChildCount();
-        builder.append("\n").append(prefix)
-            .append("child #").append(childId).append(':');
-        append(builder, "afId", node.getAutofillId());
-        append(builder, "afType", node.getAutofillType());
-        append(builder, "afValue", toString(node.getAutofillValue()));
-        append(builder, "resId", node.getIdEntry());
-        append(builder, "class", node.getClassName());
-        append(builder, "text", node.getText());
-        append(builder, "webDomain", node.getWebDomain());
-        append(builder, "checked", node.isChecked());
-        append(builder, "focused", node.isFocused());
-        final HtmlInfo htmlInfo = node.getHtmlInfo();
-        if (htmlInfo != null) {
-            builder.append(", HtmlInfo[tag=").append(htmlInfo.getTag())
-                .append(", attrs: ").append(htmlInfo.getAttributes()).append(']');
-        }
-        if (childrenSize > 0) {
-            append(builder, "#children", childrenSize).append("\n").append(prefix);
-            prefix += " ";
-            if (childrenSize > 0) {
-                for (int i = 0; i < childrenSize; i++) {
-                    dump(builder, node.getChildAt(i), prefix, i);
-                }
-            }
-        }
-    }
-
-    /**
-     * Appends a field value to a {@link StringBuilder} when it's not {@code null}.
-     */
-    @NonNull
-    public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
-            @Nullable Object value) {
-        if (value == null) return builder;
-
-        if ((value instanceof Boolean) && ((Boolean) value)) {
-            return builder.append(", ").append(field);
-        }
-
-        if (value instanceof Integer && ((Integer) value) == 0
-                || value instanceof CharSequence && TextUtils.isEmpty((CharSequence) value)) {
-            return builder;
-        }
-
-        return builder.append(", ").append(field).append('=').append(value);
-    }
-
-    /**
-     * Appends a field value to a {@link StringBuilder} when it's {@code true}.
-     */
-    @NonNull
-    public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
-            boolean value) {
-        if (value) {
-            builder.append(", ").append(field);
-        }
-        return builder;
-    }
-
-    /**
-     * Gets a node if it matches the filter criteria for the given id.
-     */
-    public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
-            @NonNull NodeFilter<ViewNode> filter) {
-        Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
-        final int nodes = structure.getWindowNodeCount();
-        for (int i = 0; i < nodes; i++) {
-            final WindowNode windowNode = structure.getWindowNodeAt(i);
-            final ViewNode rootNode = windowNode.getRootViewNode();
-            final ViewNode node = findNodeByFilter(rootNode, id, filter);
-            if (node != null) {
-                return node;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gets a node if it matches the filter criteria for the given id.
-     */
-    public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
-            @NonNull NodeFilter<ViewNode> filter) {
-        for (FillContext context : contexts) {
-            ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
-            if (node != null) {
-                return node;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gets a node if it matches the filter criteria for the given id.
-     */
-    public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
-            @NonNull NodeFilter<ViewNode> filter) {
-        if (filter.matches(node, id)) {
-            return node;
-        }
-        final int childrenSize = node.getChildCount();
-        if (childrenSize > 0) {
-            for (int i = 0; i < childrenSize; i++) {
-                final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter);
-                if (found != null) {
-                    return found;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gets a node given its Android resource id, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) {
-        return findNodeByFilter(structure, resourceId, RESOURCE_ID_FILTER);
-    }
-
-    /**
-     * Gets a node given its Android resource id, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
-        return findNodeByFilter(contexts, resourceId, RESOURCE_ID_FILTER);
-    }
-
-    /**
-     * Gets a node given its Android resource id, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByResourceId(ViewNode node, String resourceId) {
-        return findNodeByFilter(node, resourceId, RESOURCE_ID_FILTER);
-    }
-
-    /**
-     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) {
-        return findNodeByFilter(structure, htmlName, HTML_NAME_FILTER);
-    }
-
-    /**
-     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) {
-        return findNodeByFilter(contexts, htmlName, HTML_NAME_FILTER);
-    }
-
-    /**
-     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) {
-        return findNodeByFilter(node, htmlName, HTML_NAME_FILTER);
-    }
-
-    /**
-     * Gets a node given the value of its (single) autofill hint property, or {@code null} if not
-     * found.
-     */
-    public static ViewNode findNodeByAutofillHint(ViewNode node, String hint) {
-        return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER);
-    }
-
-    /**
-     * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
-     * not found.
-     */
-    public static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) {
-        return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER);
-    }
-
-    /**
-     * Gets a node given its Android resource id.
-     */
-    @NonNull
-    public static AutofillId findAutofillIdByResourceId(@NonNull FillContext context,
-            @NonNull String resourceId) {
-        final ViewNode node = findNodeByFilter(context.getStructure(), resourceId,
-                RESOURCE_ID_FILTER);
-        assertWithMessage("No node for resourceId %s", resourceId).that(node).isNotNull();
-        return node.getAutofillId();
-    }
-
-    /**
-     * Gets the {@code name} attribute of a node representing an HTML input tag.
-     */
-    @Nullable
-    public static String getHtmlName(@NonNull ViewNode node) {
-        final HtmlInfo htmlInfo = node.getHtmlInfo();
-        if (htmlInfo == null) {
-            return null;
-        }
-        final String tag = htmlInfo.getTag();
-        if (!"input".equals(tag)) {
-            Log.w(TAG, "getHtmlName(): invalid tag (" + tag + ") on " + htmlInfo);
-            return null;
-        }
-        for (Pair<String, String> attr : htmlInfo.getAttributes()) {
-            if ("name".equals(attr.first)) {
-                return attr.second;
-            }
-        }
-        Log.w(TAG, "getHtmlName(): no 'name' attribute on " + htmlInfo);
-        return null;
-    }
-
-    /**
-     * Gets a node given its expected text, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByText(AssistStructure structure, String text) {
-        return findNodeByFilter(structure, text, TEXT_FILTER);
-    }
-
-    /**
-     * Gets a node given its expected text, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByText(ViewNode node, String text) {
-        return findNodeByFilter(node, text, TEXT_FILTER);
-    }
-
-    /**
-     * Gets a view that contains the an autofill hint, or {@code null} if not found.
-     */
-    public static View findViewByAutofillHint(Activity activity, String hint) {
-        final View rootView = activity.getWindow().getDecorView().getRootView();
-        return findViewByAutofillHint(rootView, hint);
-    }
-
-    /**
-     * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if
-     * not found.
-     */
-    public static View findViewByAutofillHint(View view, String hint) {
-        if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view;
-        if ((view instanceof ViewGroup)) {
-            final ViewGroup group = (ViewGroup) view;
-            for (int i = 0; i < group.getChildCount(); i++) {
-                final View child = findViewByAutofillHint(group.getChildAt(i), hint);
-                if (child != null) return child;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Asserts a text-based node is sanitized.
-     */
-    public static void assertTextIsSanitized(ViewNode node) {
-        final CharSequence text = node.getText();
-        final String resourceId = node.getIdEntry();
-        if (!TextUtils.isEmpty(text)) {
-            throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
-        }
-
-        assertNotFromResources(node);
-        assertNodeHasNoAutofillValue(node);
-    }
-
-    private static void assertNotFromResources(ViewNode node) {
-        assertThat(node.getTextIdEntry()).isNull();
-    }
-
-    public static void assertNodeHasNoAutofillValue(ViewNode node) {
-        final AutofillValue value = node.getAutofillValue();
-        if (value != null) {
-            final String text = value.isText() ? value.getTextValue().toString() : "N/A";
-            throw new AssertionError("node has value: " + value + " text=" + text);
-        }
-    }
-
-    /**
-     * Asserts the contents of a text-based node that is also auto-fillable.
-     */
-    public static void assertTextOnly(ViewNode node, String expectedValue) {
-        assertText(node, expectedValue, false);
-        assertNotFromResources(node);
-    }
-
-    /**
-     * Asserts the contents of a text-based node that is also auto-fillable.
-     */
-    public static void assertTextOnly(AssistStructure structure, String resourceId,
-            String expectedValue) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertText(node, expectedValue, false);
-        assertNotFromResources(node);
-    }
-
-    /**
-     * Asserts the contents of a text-based node that is also auto-fillable.
-     */
-    public static void assertTextAndValue(ViewNode node, String expectedValue) {
-        assertText(node, expectedValue, true);
-        assertNotFromResources(node);
-    }
-
-    /**
-     * Asserts a text-based node exists and verify its values.
-     */
-    public static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
-            String expectedValue) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertTextAndValue(node, expectedValue);
-        return node;
-    }
-
-    /**
-     * Asserts a text-based node exists and is sanitized.
-     */
-    public static ViewNode assertValue(AssistStructure structure, String resourceId,
-            String expectedValue) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertTextValue(node, expectedValue);
-        return node;
-    }
-
-    /**
-     * Asserts the values of a text-based node whose string come from resoruces.
-     */
-    public static ViewNode assertTextFromResources(AssistStructure structure, String resourceId,
-            String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertText(node, expectedValue, isAutofillable);
-        assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry);
-        return node;
-    }
-
-    public static ViewNode assertHintFromResources(AssistStructure structure, String resourceId,
-            String expectedValue, String expectedHintIdEntry) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertThat(node.getHint()).isEqualTo(expectedValue);
-        assertThat(node.getHintIdEntry()).isEqualTo(expectedHintIdEntry);
-        return node;
-    }
-
-    private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
-        assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString())
-                .isEqualTo(expectedValue);
-        final AutofillValue value = node.getAutofillValue();
-        final AutofillId id = node.getAutofillId();
-        if (isAutofillable) {
-            assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull();
-            assertWithMessage("wrong auto-fill value on %s", id)
-                    .that(value.getTextValue().toString()).isEqualTo(expectedValue);
-        } else {
-            assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull();
-        }
-    }
-
-    /**
-     * Asserts the auto-fill value of a text-based node.
-     */
-    public static ViewNode assertTextValue(ViewNode node, String expectedText) {
-        final AutofillValue value = node.getAutofillValue();
-        final AutofillId id = node.getAutofillId();
-        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue();
-        assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString())
-                .isEqualTo(expectedText);
-        return node;
-    }
-
-    /**
-     * Asserts the auto-fill value of a list-based node.
-     */
-    public static ViewNode assertListValue(ViewNode node, int expectedIndex) {
-        final AutofillValue value = node.getAutofillValue();
-        final AutofillId id = node.getAutofillId();
-        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue();
-        assertWithMessage("wrong autofill value on %s", id).that(value.getListValue())
-                .isEqualTo(expectedIndex);
-        return node;
-    }
-
-    /**
-     * Asserts the auto-fill value of a toggle-based node.
-     */
-    public static void assertToggleValue(ViewNode node, boolean expectedToggle) {
-        final AutofillValue value = node.getAutofillValue();
-        final AutofillId id = node.getAutofillId();
-        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue();
-        assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue())
-                .isEqualTo(expectedToggle);
-    }
-
-    /**
-     * Asserts the auto-fill value of a date-based node.
-     */
-    public static void assertDateValue(Object object, AutofillValue value, int year, int month,
-            int day) {
-        assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
-
-        final Calendar cal = Calendar.getInstance();
-        cal.setTimeInMillis(value.getDateValue());
-
-        assertWithMessage("Wrong year on AutofillValue %s", value)
-            .that(cal.get(Calendar.YEAR)).isEqualTo(year);
-        assertWithMessage("Wrong month on AutofillValue %s", value)
-            .that(cal.get(Calendar.MONTH)).isEqualTo(month);
-        assertWithMessage("Wrong day on AutofillValue %s", value)
-             .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day);
-    }
-
-    /**
-     * Asserts the auto-fill value of a date-based node.
-     */
-    public static void assertDateValue(ViewNode node, int year, int month, int day) {
-        assertDateValue(node, node.getAutofillValue(), year, month, day);
-    }
-
-    /**
-     * Asserts the auto-fill value of a date-based view.
-     */
-    public static void assertDateValue(View view, int year, int month, int day) {
-        assertDateValue(view, view.getAutofillValue(), year, month, day);
-    }
-
-    /**
-     * Asserts the auto-fill value of a time-based node.
-     */
-    private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) {
-        assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
-
-        final Calendar cal = Calendar.getInstance();
-        cal.setTimeInMillis(value.getDateValue());
-
-        assertWithMessage("Wrong hour on AutofillValue %s", value)
-            .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour);
-        assertWithMessage("Wrong minute on AutofillValue %s", value)
-            .that(cal.get(Calendar.MINUTE)).isEqualTo(minute);
-    }
-
-    /**
-     * Asserts the auto-fill value of a time-based node.
-     */
-    public static void assertTimeValue(ViewNode node, int hour, int minute) {
-        assertTimeValue(node, node.getAutofillValue(), hour, minute);
-    }
-
-    /**
-     * Asserts the auto-fill value of a time-based view.
-     */
-    public static void assertTimeValue(View view, int hour, int minute) {
-        assertTimeValue(view, view.getAutofillValue(), hour, minute);
-    }
-
-    /**
-     * Asserts a text-based node exists and is sanitized.
-     */
-    public static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
-        assertTextIsSanitized(node);
-        return node;
-    }
-
-    /**
-     * Asserts a list-based node exists and is sanitized.
-     */
-    public static void assertListValueIsSanitized(AssistStructure structure, String resourceId) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
-        assertTextIsSanitized(node);
-    }
-
-    /**
-     * Asserts a toggle node exists and is sanitized.
-     */
-    public static void assertToggleIsSanitized(AssistStructure structure, String resourceId) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertNodeHasNoAutofillValue(node);
-        assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked())
-                .isFalse();
-    }
-
-    /**
-     * Asserts a node exists and has the {@code expected} number of children.
-     */
-    public static void assertNumberOfChildren(AssistStructure structure, String resourceId,
-            int expected) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        final int actual = node.getChildCount();
-        if (actual != expected) {
-            dumpStructure("assertNumberOfChildren()", structure);
-            throw new AssertionError("assertNumberOfChildren() for " + resourceId
-                    + " failed: expected " + expected + ", got " + actual);
-        }
-    }
-
-    /**
-     * Asserts the number of children in the Assist structure.
-     */
-    public static void assertNumberOfChildren(AssistStructure structure, int expected) {
-        assertWithMessage("wrong number of nodes").that(structure.getWindowNodeCount())
-                .isEqualTo(1);
-        final int actual = getNumberNodes(structure);
-        if (actual != expected) {
-            dumpStructure("assertNumberOfChildren()", structure);
-            throw new AssertionError("assertNumberOfChildren() for structure failed: expected "
-                    + expected + ", got " + actual);
-        }
-    }
-
-    /**
-     * Gets the total number of nodes in an structure.
-     */
-    public static int getNumberNodes(AssistStructure structure) {
-        int count = 0;
-        final int nodes = structure.getWindowNodeCount();
-        for (int i = 0; i < nodes; i++) {
-            final WindowNode windowNode = structure.getWindowNodeAt(i);
-            final ViewNode rootNode = windowNode.getRootViewNode();
-            count += getNumberNodes(rootNode);
-        }
-        return count;
-    }
-
-    /**
-     * Gets the total number of nodes in an node, including all descendants and the node itself.
-     */
-    public static int getNumberNodes(ViewNode node) {
-        int count = 1;
-        final int childrenSize = node.getChildCount();
-        if (childrenSize > 0) {
-            for (int i = 0; i < childrenSize; i++) {
-                count += getNumberNodes(node.getChildAt(i));
-            }
-        }
-        return count;
-    }
-
-    /**
-     * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given
-     * {@code resourceIds}.
-     */
-    public static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver,
-            String[] resourceIds) {
-        if (resourceIds == null) return null;
-
-        final AutofillId[] requiredIds = new AutofillId[resourceIds.length];
-        for (int i = 0; i < resourceIds.length; i++) {
-            final String resourceId = resourceIds[i];
-            final ViewNode node = nodeResolver.apply(resourceId);
-            if (node == null) {
-                throw new AssertionError("No node with resourceId " + resourceId);
-            }
-            requiredIds[i] = node.getAutofillId();
-
-        }
-        return requiredIds;
-    }
-
-    /**
-     * Get an {@link AutofillId} mapped from the {@code structure} node with the given
-     * {@code resourceId}.
-     */
-    public static AutofillId getAutofillId(Function<String, ViewNode> nodeResolver,
-            String resourceId) {
-        if (resourceId == null) return null;
-
-        final ViewNode node = nodeResolver.apply(resourceId);
-        if (node == null) {
-            throw new AssertionError("No node with resourceId " + resourceId);
-        }
-        return node.getAutofillId();
-    }
-
-    /**
-     * Prevents the screen to rotate by itself
-     */
-    public static void disableAutoRotation(UiBot uiBot) throws Exception {
-        runShellCommand(ACCELLEROMETER_CHANGE, 0);
-        uiBot.setScreenOrientation(PORTRAIT);
-    }
-
-    /**
-     * Allows the screen to rotate by itself
-     */
-    public static void allowAutoRotation() {
-        runShellCommand(ACCELLEROMETER_CHANGE, 1);
-    }
-
-    /**
-     * Gets the maximum number of partitions per session.
-     */
-    public static int getMaxPartitions() {
-        return Integer.parseInt(runShellCommand("cmd autofill get max_partitions"));
-    }
-
-    /**
-     * Sets the maximum number of partitions per session.
-     */
-    public static void setMaxPartitions(int value) throws Exception {
-        runShellCommand("cmd autofill set max_partitions %d", value);
-        SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_partitions", () -> {
-            return getMaxPartitions() == value ? Boolean.TRUE : null;
-        });
-    }
-
-    /**
-     * Gets the maximum number of visible datasets.
-     */
-    public static int getMaxVisibleDatasets() {
-        return Integer.parseInt(runShellCommand("cmd autofill get max_visible_datasets"));
-    }
-
-    /**
-     * Sets the maximum number of visible datasets.
-     */
-    public static void setMaxVisibleDatasets(int value) throws Exception {
-        runShellCommand("cmd autofill set max_visible_datasets %d", value);
-        SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_visible_datasets", () -> {
-            return getMaxVisibleDatasets() == value ? Boolean.TRUE : null;
-        });
-    }
-
-    /**
-     * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi.
-     */
-    public static boolean isAutofillWindowFullScreen(Context context) {
-        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-    }
-
-    /**
-     * Checks if screen orientation can be changed.
-     */
-    public static boolean isRotationSupported(Context context) {
-        final PackageManager packageManager = context.getPackageManager();
-        if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-            Log.v(TAG, "isRotationSupported(): is auto");
-            return false;
-        }
-        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
-            Log.v(TAG, "isRotationSupported(): has leanback feature");
-            return false;
-        }
-        if (packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) {
-            Log.v(TAG, "isRotationSupported(): is PC");
-            return false;
-        }
-        return true;
-    }
-
-    private static boolean getBoolean(Context context, String id) {
-        final Resources resources = context.getResources();
-        final int booleanId = resources.getIdentifier(id, "bool", "android");
-        return resources.getBoolean(booleanId);
-    }
-
-    /**
-     * Uses Shell command to get the Autofill logging level.
-     */
-    public static String getLoggingLevel() {
-        return runShellCommand("cmd autofill get log_level");
-    }
-
-    /**
-     * Uses Shell command to set the Autofill logging level.
-     */
-    public static void setLoggingLevel(String level) {
-        runShellCommand("cmd autofill set log_level %s", level);
-    }
-
-    /**
-     * Uses Settings to enable the given autofill service for the default user, and checks the
-     * value was properly check, throwing an exception if it was not.
-     */
-    public static void enableAutofillService(@NonNull Context context,
-            @NonNull String serviceName) {
-        if (isAutofillServiceEnabled(serviceName)) return;
-
-        // Sets the setting synchronously. Note that the config itself is sets synchronously but
-        // launch of the service is asynchronous after the config is updated.
-        SettingsUtils.syncSet(context, AUTOFILL_SERVICE, serviceName);
-
-        // Waits until the service is actually enabled.
-        try {
-            Timeouts.CONNECTION_TIMEOUT.run("Enabling Autofill service", () -> {
-                return isAutofillServiceEnabled(serviceName) ? serviceName : null;
-            });
-        } catch (Exception e) {
-            throw new AssertionError("Enabling Autofill service failed.");
-        }
-    }
-
-    /**
-     * Uses Settings to disable the given autofill service for the default user, and waits until
-     * the setting is deleted.
-     */
-    public static void disableAutofillService(@NonNull Context context) {
-        final String currentService = SettingsUtils.get(AUTOFILL_SERVICE);
-        if (currentService == null) {
-            Log.v(TAG, "disableAutofillService(): already disabled");
-            return;
-        }
-        Log.v(TAG, "Disabling " + currentService);
-        SettingsUtils.syncDelete(context, AUTOFILL_SERVICE);
-    }
-
-    /**
-     * Checks whether the given service is set as the autofill service for the default user.
-     */
-    public static boolean isAutofillServiceEnabled(@NonNull String serviceName) {
-        final String actualName = getAutofillServiceName();
-        return serviceName.equals(actualName);
-    }
-
-    /**
-     * Gets then name of the autofill service for the default user.
-     */
-    public static String getAutofillServiceName() {
-        return SettingsUtils.get(AUTOFILL_SERVICE);
-    }
-
-    /**
-     * Asserts whether the given service is enabled as the autofill service for the default user.
-     */
-    public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) {
-        final String actual = SettingsUtils.get(AUTOFILL_SERVICE);
-        final String expected = enabled ? serviceName : null;
-        assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE)
-                .that(actual).isEqualTo(expected);
-    }
-
-    /**
-     * Enables / disables the default augmented autofill service.
-     */
-    public static void setDefaultAugmentedAutofillServiceEnabled(boolean enabled) {
-        Log.d(TAG, "setDefaultAugmentedAutofillServiceEnabled(): " + enabled);
-        runShellCommand("cmd autofill set default-augmented-service-enabled 0 %s",
-                Boolean.toString(enabled));
-    }
-
-    /**
-     * Gets the instrumentation context.
-     */
-    public static Context getContext() {
-        return InstrumentationRegistry.getInstrumentation().getContext();
-    }
-
-    /**
-     * Asserts the node has an {@code HTMLInfo} property, with the given tag.
-     */
-    public static HtmlInfo assertHasHtmlTag(ViewNode node, String expectedTag) {
-        final HtmlInfo info = node.getHtmlInfo();
-        assertWithMessage("node doesn't have htmlInfo").that(info).isNotNull();
-        assertWithMessage("wrong tag").that(info.getTag()).isEqualTo(expectedTag);
-        return info;
-    }
-
-    /**
-     * Gets the value of an {@code HTMLInfo} attribute.
-     */
-    @Nullable
-    public static String getAttributeValue(HtmlInfo info, String attribute) {
-        for (Pair<String, String> pair : info.getAttributes()) {
-            if (pair.first.equals(attribute)) {
-                return pair.second;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Asserts a {@code HTMLInfo} has an attribute with a given value.
-     */
-    public static void assertHasAttribute(HtmlInfo info, String attribute, String expectedValue) {
-        final String actualValue = getAttributeValue(info, attribute);
-        assertWithMessage("Attribute %s not found", attribute).that(actualValue).isNotNull();
-        assertWithMessage("Wrong value for Attribute %s", attribute)
-            .that(actualValue).isEqualTo(expectedValue);
-    }
-
-    /**
-     * Finds a {@link WebView} node given its expected form name.
-     */
-    public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) {
-        return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER);
-    }
-
-    private static void assertClientState(Object container, Bundle clientState,
-            String key, String value) {
-        assertWithMessage("'%s' should have client state", container)
-            .that(clientState).isNotNull();
-        assertWithMessage("Wrong number of client state extras on '%s'", container)
-            .that(clientState.keySet().size()).isEqualTo(1);
-        assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container)
-            .that(clientState.getString(key)).isEqualTo(value);
-    }
-
-    /**
-     * Asserts the content of a {@link FillEventHistory#getClientState()}.
-     *
-     * @param history event to be asserted
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    @SuppressWarnings("javadoc")
-    public static void assertDeprecatedClientState(@NonNull FillEventHistory history,
-            @NonNull String key, @NonNull String value) {
-        assertThat(history).isNotNull();
-        @SuppressWarnings("deprecation")
-        final Bundle clientState = history.getClientState();
-        assertClientState(history, clientState, key, value);
-    }
-
-    /**
-     * Asserts the {@link FillEventHistory#getClientState()} is not set.
-     *
-     * @param history event to be asserted
-     */
-    @SuppressWarnings("javadoc")
-    public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) {
-        assertThat(history).isNotNull();
-        @SuppressWarnings("deprecation")
-        final Bundle clientState = history.getClientState();
-        assertWithMessage("History '%s' should not have client state", history)
-             .that(clientState).isNull();
-    }
-
-    /**
-     * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}.
-     *
-     * @param event event to be asserted
-     * @param eventType expected type
-     * @param datasetId dataset set id expected in the event
-     * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't
-     * have client state)
-     * @param value the only value expected in the client state bundle (or {@code null} if it
-     * shouldn't have client state)
-     * @param fieldClassificationResults expected results when asserting field classification
-     */
-    private static void assertFillEvent(@NonNull FillEventHistory.Event event,
-            int eventType, @Nullable String datasetId,
-            @Nullable String key, @Nullable String value,
-            @Nullable FieldClassificationResult[] fieldClassificationResults) {
-        assertThat(event).isNotNull();
-        assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType);
-        if (datasetId == null) {
-            assertWithMessage("Event %s should not have dataset id", event)
-                .that(event.getDatasetId()).isNull();
-        } else {
-            assertWithMessage("Wrong dataset id for %s", event)
-                .that(event.getDatasetId()).isEqualTo(datasetId);
-        }
-        final Bundle clientState = event.getClientState();
-        if (key == null) {
-            assertWithMessage("Event '%s' should not have client state", event)
-                .that(clientState).isNull();
-        } else {
-            assertClientState(event, clientState, key, value);
-        }
-        assertWithMessage("Event '%s' should not have selected datasets", event)
-                .that(event.getSelectedDatasetIds()).isEmpty();
-        assertWithMessage("Event '%s' should not have ignored datasets", event)
-                .that(event.getIgnoredDatasetIds()).isEmpty();
-        assertWithMessage("Event '%s' should not have changed fields", event)
-                .that(event.getChangedFields()).isEmpty();
-        assertWithMessage("Event '%s' should not have manually-entered fields", event)
-                .that(event.getManuallyEnteredField()).isEmpty();
-        final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification();
-        if (fieldClassificationResults == null) {
-            assertThat(detectedFields).isEmpty();
-        } else {
-            assertThat(detectedFields).hasSize(fieldClassificationResults.length);
-            int i = 0;
-            for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) {
-                assertMatches(i, entry, fieldClassificationResults[i]);
-                i++;
-            }
-        }
-    }
-
-    private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult,
-            FieldClassificationResult expectedResult) {
-        assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey())
-                .isEqualTo(expectedResult.id);
-        final List<Match> matches = actualResult.getValue().getMatches();
-        assertWithMessage("Wrong number of matches: " + matches).that(matches.size())
-                .isEqualTo(expectedResult.categoryIds.length);
-        for (int j = 0; j < matches.size(); j++) {
-            final Match match = matches.get(j);
-            assertWithMessage("Wrong categoryId at (%s, %s): %s", i, j, match)
-                .that(match.getCategoryId()).isEqualTo(expectedResult.categoryIds[j]);
-            assertWithMessage("Wrong score at (%s, %s): %s", i, j, match)
-                .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]);
-        }
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     */
-    public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
-            @Nullable String datasetId) {
-        assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
-            @Nullable String datasetId, @Nullable String key, @Nullable String value) {
-        assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
-            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
-        assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     */
-    public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
-            @Nullable String datasetId) {
-        assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
-     *
-     * @param event event to be asserted
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
-            @NonNull String key, @NonNull String value) {
-        assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
-     *
-     * @param event event to be asserted
-     */
-    public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event) {
-        assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED}
-     * event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    public static void assertFillEventForDatasetAuthenticationSelected(
-            @NonNull FillEventHistory.Event event,
-            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
-        assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    public static void assertFillEventForAuthenticationSelected(
-            @NonNull FillEventHistory.Event event,
-            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
-        assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null);
-    }
-
-    public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
-            @NonNull AutofillId fieldId, @NonNull String categoryId, float score) {
-        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null,
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId, categoryId, score)
-                });
-    }
-
-    public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
-            @NonNull FieldClassificationResult[] results) {
-        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results);
-    }
-
-    public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) {
-        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null);
-    }
-
-    @NonNull
-    public static String getActivityName(List<FillContext> contexts) {
-        if (contexts == null) return "N/A (null contexts)";
-
-        if (contexts.isEmpty()) return "N/A (empty contexts)";
-
-        final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure();
-        if (structure == null) return "N/A (no AssistStructure)";
-
-        final ComponentName componentName = structure.getActivityComponent();
-        if (componentName == null) return "N/A (no component name)";
-
-        return componentName.flattenToShortString();
-    }
-
-    public static void assertFloat(float actualValue, float expectedValue) {
-        assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
-    }
-
-    public static void assertHasFlags(int actualFlags, int expectedFlags) {
-        assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags)
-                .that(actualFlags & expectedFlags).isEqualTo(expectedFlags);
-    }
-
-    public static String callbackEventAsString(int event) {
-        switch (event) {
-            case AutofillCallback.EVENT_INPUT_HIDDEN:
-                return "HIDDEN";
-            case AutofillCallback.EVENT_INPUT_SHOWN:
-                return "SHOWN";
-            case AutofillCallback.EVENT_INPUT_UNAVAILABLE:
-                return "UNAVAILABLE";
-            default:
-                return "UNKNOWN:" + event;
-        }
-    }
-
-    public static String importantForAutofillAsString(int mode) {
-        switch (mode) {
-            case View.IMPORTANT_FOR_AUTOFILL_AUTO:
-                return "IMPORTANT_FOR_AUTOFILL_AUTO";
-            case View.IMPORTANT_FOR_AUTOFILL_YES:
-                return "IMPORTANT_FOR_AUTOFILL_YES";
-            case View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS:
-                return "IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS";
-            case View.IMPORTANT_FOR_AUTOFILL_NO:
-                return "IMPORTANT_FOR_AUTOFILL_NO";
-            case View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS:
-                return "IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS";
-            default:
-                return "UNKNOWN:" + mode;
-        }
-    }
-
-    public static boolean hasHint(@Nullable String[] hints, @Nullable Object expectedHint) {
-        if (hints == null || expectedHint == null) return false;
-        for (String actualHint : hints) {
-            if (expectedHint.equals(actualHint)) return true;
-        }
-        return false;
-    }
-
-    public static Bundle newClientState(String key, String value) {
-        final Bundle clientState = new Bundle();
-        clientState.putString(key, value);
-        return clientState;
-    }
-
-    public static void assertAuthenticationClientState(String where, Bundle data,
-            String expectedKey, String expectedValue) {
-        assertWithMessage("no client state on %s", where).that(data).isNotNull();
-        final String extraValue = data.getString(expectedKey);
-        assertWithMessage("invalid value for %s on %s", expectedKey, where)
-                .that(extraValue).isEqualTo(expectedValue);
-    }
-
-    /**
-     * Asserts that 2 bitmaps have are the same. If they aren't throws an exception and dump them
-     * locally so their can be visually inspected.
-     *
-     * @param filename base name of the files generated in case of error
-     * @param bitmap1 first bitmap to be compared
-     * @param bitmap2 second bitmap to be compared
-     */
-    // TODO: move to common code
-    public static void assertBitmapsAreSame(@NonNull String filename, @Nullable Bitmap bitmap1,
-            @Nullable Bitmap bitmap2) throws IOException {
-        assertWithMessage("1st bitmap is null").that(bitmap1).isNotNull();
-        assertWithMessage("2nd bitmap is null").that(bitmap2).isNotNull();
-        final boolean same = bitmap1.sameAs(bitmap2);
-        if (same) {
-            Log.v(TAG, "bitmap comparison passed for " + filename);
-            return;
-        }
-
-        final File dir = getLocalDirectory();
-        if (dir == null) {
-            throw new AssertionError("bitmap comparison failed for " + filename
-                    + ", and bitmaps could not be dumped on " + dir);
-        }
-        final File dump1 = dumpBitmap(bitmap1, dir, filename + "-1.png");
-        final File dump2 = dumpBitmap(bitmap2, dir, filename + "-2.png");
-        throw new AssertionError(
-                "bitmap comparison failed; check contents of " + dump1 + " and " + dump2);
-    }
-
-    @Nullable
-    private static File getLocalDirectory() {
-        final File dir = new File(LOCAL_DIRECTORY);
-        dir.mkdirs();
-        if (!dir.exists()) {
-            Log.e(TAG, "Could not create directory " + dir);
-            return null;
-        }
-        return dir;
-    }
-
-    @Nullable
-    private static File createFile(@NonNull File dir, @NonNull String filename) throws IOException {
-        final File file = new File(dir, filename);
-        if (file.exists()) {
-            Log.v(TAG, "Deleting file " + file);
-            file.delete();
-        }
-        if (!file.createNewFile()) {
-            Log.e(TAG, "Could not create file " + file);
-            return null;
-        }
-        return file;
-    }
-
-    @Nullable
-    private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir,
-            @NonNull String filename) throws IOException {
-        final File file = createFile(dir, filename);
-        if (file != null) {
-            dumpBitmap(bitmap, file);
-
-        }
-        return file;
-    }
-
-    @Nullable
-    public static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File file) {
-        Log.i(TAG, "Dumping bitmap at " + file);
-        BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName());
-        return file;
-    }
-
-    /**
-     * Creates a file in the device, using the name of the current test as a prefix.
-     */
-    @Nullable
-    public static File createTestFile(@NonNull String name) throws IOException {
-        final File dir = getLocalDirectory();
-        if (dir == null) return null;
-
-        final String prefix = TestNameUtils.getCurrentTestName().replaceAll("\\.|\\(|\\/", "_")
-                .replaceAll("\\)", "");
-        final String filename = prefix + "-" + name;
-
-        return createFile(dir, filename);
-    }
-
-    /**
-     * Offers an object to a queue or times out.
-     *
-     * @return {@code true} if the offer was accepted, {$code false} if it timed out or was
-     * interrupted.
-     */
-    public static <T> boolean offer(BlockingQueue<T> queue, T obj, long timeoutMs) {
-        boolean offered = false;
-        try {
-            offered = queue.offer(obj, timeoutMs, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            Log.w(TAG, "interrupted offering", e);
-            Thread.currentThread().interrupt();
-        }
-        if (!offered) {
-            Log.e(TAG, "could not offer " + obj + " in " + timeoutMs + "ms");
-        }
-        return offered;
-    }
-
-    /**
-     * Calls this method to assert given {@code string} is equal to {@link #LARGE_STRING}, as
-     * comparing its value using standard assertions might ANR.
-     */
-    public static void assertEqualsToLargeString(@NonNull String string) {
-        assertThat(string).isNotNull();
-        assertThat(string).hasLength(LARGE_STRING_SIZE);
-        assertThat(string.charAt(0)).isEqualTo(LARGE_STRING_CHAR);
-        assertThat(string.charAt(LARGE_STRING_SIZE - 1)).isEqualTo(LARGE_STRING_CHAR);
-    }
-
-    /**
-     * Asserts that autofill is enabled in the context, retrying if necessariy.
-     */
-    public static void assertAutofillEnabled(@NonNull Context context, boolean expected)
-            throws Exception {
-        assertAutofillEnabled(context.getSystemService(AutofillManager.class), expected);
-    }
-
-    /**
-     * Asserts that autofill is enabled in the manager, retrying if necessariy.
-     */
-    public static void assertAutofillEnabled(@NonNull AutofillManager afm, boolean expected)
-            throws Exception {
-        Timeouts.IDLE_UNBIND_TIMEOUT.run("assertEnabled(" + expected + ")", () -> {
-            final boolean actual = afm.isEnabled();
-            Log.v(TAG, "assertEnabled(): expected=" + expected + ", actual=" + actual);
-            return actual == expected ? "not_used" : null;
-        });
-    }
-
-    /**
-     * Asserts these autofill ids are the same, except for the session.
-     */
-    public static void assertEqualsIgnoreSession(@NonNull AutofillId id1, @NonNull AutofillId id2) {
-        assertWithMessage("id1 is null").that(id1).isNotNull();
-        assertWithMessage("id2 is null").that(id2).isNotNull();
-        assertWithMessage("%s is not equal to %s", id1, id2).that(id1.equalsIgnoreSession(id2))
-                .isTrue();
-    }
-
-    /**
-     * Asserts {@link View#isAutofilled()} state of the given view, waiting if necessarity to avoid
-     * race conditions.
-     */
-    public static void assertViewAutofillState(@NonNull View view, boolean expected)
-            throws Exception {
-        Timeouts.FILL_TIMEOUT.run("assertViewAutofillState(" + view + ", " + expected + ")",
-                () -> {
-                    final boolean actual = view.isAutofilled();
-                    Log.v(TAG, "assertViewAutofillState(): expected=" + expected + ", actual="
-                            + actual);
-                    return actual == expected ? "not_used" : null;
-                });
-    }
-
-    /**
-     * Allows the test to draw overlaid windows.
-     *
-     * <p>Should call {@link #disallowOverlays()} afterwards.
-     */
-    public static void allowOverlays() {
-        ShellUtils.setOverlayPermissions(MY_PACKAGE, true);
-    }
-
-    /**
-     * Disallow the test to draw overlaid windows.
-     *
-     * <p>Should call {@link #disallowOverlays()} afterwards.
-     */
-    public static void disallowOverlays() {
-        ShellUtils.setOverlayPermissions(MY_PACKAGE, false);
-    }
-
-    public static RemoteViews createPresentation(String message) {
-        final RemoteViews presentation = new RemoteViews(getContext()
-                .getPackageName(), R.layout.list_item);
-        presentation.setTextViewText(R.id.text1, message);
-        return presentation;
-    }
-
-    public static InlinePresentation createInlinePresentation(String message) {
-        final PendingIntent dummyIntent =
-                PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
-        return createInlinePresentation(message, dummyIntent, false);
-    }
-
-    public static InlinePresentation createInlinePresentation(String message,
-            PendingIntent attribution) {
-        return createInlinePresentation(message, attribution, false);
-    }
-
-    public static InlinePresentation createPinnedInlinePresentation(String message) {
-        final PendingIntent dummyIntent =
-                PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
-        return createInlinePresentation(message, dummyIntent, true);
-    }
-
-    private static InlinePresentation createInlinePresentation(@NonNull String message,
-            @NonNull PendingIntent attribution, boolean pinned) {
-        return new InlinePresentation(
-                InlineSuggestionUi.newContentBuilder(attribution)
-                        .setTitle(message).build().getSlice(),
-                new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
-                        .build(), /* pinned= */ pinned);
-    }
-
-    public static void mockSwitchInputMethod(@NonNull Context context) throws Exception {
-        final ContentResolver cr = context.getContentResolver();
-        final int subtype = Settings.Secure.getInt(cr, SELECTED_INPUT_METHOD_SUBTYPE);
-        Settings.Secure.putInt(cr, SELECTED_INPUT_METHOD_SUBTYPE, subtype);
-    }
-
-    private Helper() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-
-    static class FieldClassificationResult {
-        public final AutofillId id;
-        public final String[] categoryIds;
-        public final float[] scores;
-
-        FieldClassificationResult(@NonNull AutofillId id, @NonNull String categoryId, float score) {
-            this(id, new String[] { categoryId }, new float[] { score });
-        }
-
-        FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] categoryIds,
-                float[] scores) {
-            this.id = id;
-            this.categoryIds = categoryIds;
-            this.scores = scores;
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/IdMode.java b/tests/autofillservice/src/android/autofillservice/cts/IdMode.java
deleted file mode 100644
index 66e857b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/IdMode.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-/**
- * Enum used to explain the meaning of node ids used by test cases.
- */
-enum IdMode {
-    RESOURCE_ID,
-    HTML_NAME,
-    HTML_NAME_OR_RESOURCE_ID
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
deleted file mode 100644
index 06bb218..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.only;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.ImageTransformation;
-import android.service.autofill.ValueFinder;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.regex.Pattern;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class ImageTransformationTest {
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void testAllNullBuilder() {
-        assertThrows(NullPointerException.class,
-                () ->  new ImageTransformation.Builder(null, null, 0));
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void testNullAutofillIdBuilder() {
-        assertThrows(NullPointerException.class,
-                () ->  new ImageTransformation.Builder(null, Pattern.compile(""), 1));
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void testNullRegexBuilder() {
-        assertThrows(NullPointerException.class,
-                () ->  new ImageTransformation.Builder(new AutofillId(1), null, 1));
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void testNullSubstBuilder() {
-        assertThrows(IllegalArgumentException.class,
-                () ->  new ImageTransformation.Builder(new AutofillId(1), Pattern.compile(""), 0));
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void fieldCannotBeFound() throws Exception {
-        AutofillId unknownId = new AutofillId(42);
-
-        ImageTransformation trans = new ImageTransformation
-                .Builder(unknownId, Pattern.compile("val"), 1)
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(unknownId)).thenReturn(null);
-
-        trans.apply(finder, template, 0);
-
-        // if a view cannot be found, nothing is set
-        verify(template, never()).setImageViewResource(anyInt(), anyInt());
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void theOneOptionsMatches() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*"), 42)
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("val");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setImageViewResource(0, 42);
-    }
-
-    @Test
-    public void theOneOptionsMatchesWithContentDescription() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*"), 42, "Are you content?")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("val");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setImageViewResource(0, 42);
-        verify(template).setContentDescription(0, "Are you content?");
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void noOptionsMatches() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile("val"), 42)
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("bad-val");
-
-        trans.apply(finder, template, 0);
-
-        verify(template, never()).setImageViewResource(anyInt(), anyInt());
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void multipleOptionsOneMatches() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*1"), 1)
-                .addOption(Pattern.compile(".*2"), 2)
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("val-2");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setImageViewResource(0, 2);
-    }
-
-    @Test
-    public void multipleOptionsOneMatchesWithContentDescription() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*1"), 1, "Are you content?")
-                .addOption(Pattern.compile(".*2"), 2, "I am content")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("val-2");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setImageViewResource(0, 2);
-        verify(template).setContentDescription(0, "I am content");
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void twoOptionsMatch() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*a.*"), 1)
-                .addOption(Pattern.compile(".*b.*"), 2)
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("ab");
-
-        trans.apply(finder, template, 0);
-
-        // If two options match, the first one is picked
-        verify(template, only()).setImageViewResource(0, 1);
-    }
-
-    @Test
-    public void twoOptionsMatchWithContentDescription() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*a.*"), 1, "Are you content?")
-                .addOption(Pattern.compile(".*b.*"), 2, "No, I'm not")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("ab");
-
-        trans.apply(finder, template, 0);
-
-        // If two options match, the first one is picked
-        verify(template).setImageViewResource(0, 1);
-        verify(template).setContentDescription(0, "Are you content?");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivity.java
deleted file mode 100644
index 6bef659..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-public class InitializedCheckoutActivity extends CheckoutActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.initialized_checkout_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java
deleted file mode 100644
index 43000cc..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.CheckoutActivity.ID_ADDRESS;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_EXPIRATION;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_NUMBER;
-import static android.autofillservice.cts.CheckoutActivity.ID_SAVE_CC;
-import static android.autofillservice.cts.CheckoutActivity.INDEX_ADDRESS_HOME;
-import static android.autofillservice.cts.Helper.assertListValue;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertToggleValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.platform.test.annotations.AppModeFull;
-
-import org.junit.Test;
-
-/**
- * Test case for an activity containing non-TextField views with initial values set on XML.
- */
-@AppModeFull(reason = "CheckoutActivityTest() is enough")
-public class InitializedCheckoutActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<InitializedCheckoutActivity> {
-
-    private InitializedCheckoutActivity mCheckoutActivity;
-
-    @Override
-    protected AutofillActivityTestRule<InitializedCheckoutActivity> getActivityRule() {
-        return new AutofillActivityTestRule<InitializedCheckoutActivity>(
-                InitializedCheckoutActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mCheckoutActivity = getActivity();
-            }
-        };
-
-    }
-
-    @Test
-    public void testSanitization() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger auto-fill.
-        mCheckoutActivity.onCcNumber((v) -> v.requestFocus());
-
-        // Assert sanitization: most everything should be available...
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_CC_NUMBER), "4815162342");
-        assertListValue(findNodeByResourceId(fillRequest.structure, ID_ADDRESS),
-                INDEX_ADDRESS_HOME);
-        assertToggleValue(findNodeByResourceId(fillRequest.structure, ID_SAVE_CC), true);
-
-        // ... except Spinner, whose initial value cannot be set by resources:
-        assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InlinePresentationTest.java b/tests/autofillservice/src/android/autofillservice/cts/InlinePresentationTest.java
deleted file mode 100644
index b4ff09e..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/InlinePresentationTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceSpec;
-import android.net.Uri;
-import android.os.Parcel;
-import android.service.autofill.InlinePresentation;
-import android.util.Size;
-import android.widget.inline.InlinePresentationSpec;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InlinePresentationTest {
-
-    @Test
-    public void testNullInlinePresentationSpecsThrowsException() {
-        assertThrows(NullPointerException.class,
-                () -> createInlinePresentation(/* createSlice */true, /* createSpec */  false));
-    }
-
-    @Test
-    public void testNullSliceThrowsException() {
-        assertThrows(NullPointerException.class,
-                () -> createInlinePresentation(/* createSlice */false, /* createSpec */  true));
-    }
-
-    @Test
-    public void testInlinePresentationValues() {
-        InlinePresentation presentation =
-                createInlinePresentation(/* createSlice */true, /* createSpec */  true);
-
-        assertThat(presentation.isPinned()).isFalse();
-        assertThat(presentation.getInlinePresentationSpec()).isNotNull();
-        assertThat(presentation.getSlice()).isNotNull();
-        assertThat(presentation.getSlice().getItems().size()).isEqualTo(0);
-    }
-
-    @Test
-    public void testtInlinePresentationParcelizeDeparcelize() {
-        InlinePresentation presentation =
-                createInlinePresentation(/* createSlice */true, /* createSpec */  true);
-
-        Parcel p = Parcel.obtain();
-        presentation.writeToParcel(p, 0);
-        p.setDataPosition(0);
-
-        InlinePresentation targetPresentation = InlinePresentation.CREATOR.createFromParcel(p);
-        p.recycle();
-
-        assertThat(targetPresentation.isPinned()).isEqualTo(presentation.isPinned());
-        assertThat(targetPresentation.getInlinePresentationSpec()).isEqualTo(
-                presentation.getInlinePresentationSpec());
-        assertThat(targetPresentation.getSlice().getUri()).isEqualTo(
-                presentation.getSlice().getUri());
-        assertThat(targetPresentation.getSlice().getSpec()).isEqualTo(
-                presentation.getSlice().getSpec());
-    }
-
-    private InlinePresentation createInlinePresentation(boolean createSlice, boolean createSpec) {
-        Slice slice = createSlice ? new Slice.Builder(Uri.parse("testuri"),
-                new SliceSpec("type", 1)).build() : null;
-        InlinePresentationSpec spec = createSpec ? new InlinePresentationSpec.Builder(
-                new Size(100, 100), new Size(400, 100)).build() : null;
-        return new InlinePresentation(slice, spec, /* pined */ false);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
deleted file mode 100644
index 9e246df..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ /dev/null
@@ -1,737 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.ResponseType.FAILURE;
-import static android.autofillservice.cts.CannedFillResponse.ResponseType.NULL;
-import static android.autofillservice.cts.CannedFillResponse.ResponseType.TIMEOUT;
-import static android.autofillservice.cts.Helper.dumpStructure;
-import static android.autofillservice.cts.Helper.getActivityName;
-import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.FILL_EVENTS_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.IDLE_UNBIND_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.RESPONSE_DELAY_MS;
-import static android.autofillservice.cts.Timeouts.SAVE_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.CannedFillResponse.ResponseType;
-import android.content.ComponentName;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.SystemClock;
-import android.service.autofill.AutofillService;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillCallback;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.FillEventHistory.Event;
-import android.service.autofill.FillResponse;
-import android.service.autofill.SaveCallback;
-import android.util.Log;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-import androidx.annotation.Nullable;
-
-import com.android.compatibility.common.util.RetryableException;
-import com.android.compatibility.common.util.TestNameUtils;
-import com.android.compatibility.common.util.Timeout;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Implementation of {@link AutofillService} used in the tests.
- */
-public class InstrumentedAutoFillService extends AutofillService {
-
-    static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
-    static final String SERVICE_CLASS = "InstrumentedAutoFillService";
-
-    static final String SERVICE_NAME = SERVICE_PACKAGE + "/." + SERVICE_CLASS;
-
-    // TODO(b/125844305): remove once fixed
-    private static final boolean FAIL_ON_INVALID_CONNECTION_STATE = false;
-
-    private static final String TAG = "InstrumentedAutoFillService";
-
-    private static final boolean DUMP_FILL_REQUESTS = false;
-    private static final boolean DUMP_SAVE_REQUESTS = false;
-
-    protected static final AtomicReference<InstrumentedAutoFillService> sInstance =
-            new AtomicReference<>();
-    private static final Replier sReplier = new Replier();
-
-    private static AtomicBoolean sConnected = new AtomicBoolean(false);
-
-    protected static String sServiceLabel = SERVICE_CLASS;
-
-    // We must handle all requests in a separate thread as the service's main thread is the also
-    // the UI thread of the test process and we don't want to hose it in case of failures here
-    private static final HandlerThread sMyThread = new HandlerThread("MyServiceThread");
-    private final Handler mHandler;
-
-    private boolean mConnected;
-
-    static {
-        Log.i(TAG, "Starting thread " + sMyThread);
-        sMyThread.start();
-    }
-
-    public InstrumentedAutoFillService() {
-        sInstance.set(this);
-        sServiceLabel = SERVICE_CLASS;
-        mHandler = Handler.createAsync(sMyThread.getLooper());
-        sReplier.setHandler(mHandler);
-    }
-
-    private static InstrumentedAutoFillService peekInstance() {
-        return sInstance.get();
-    }
-
-    /**
-     * Gets the list of fill events in the {@link FillEventHistory}, waiting until it has the
-     * expected size.
-     */
-    public static List<Event> getFillEvents(int expectedSize) throws Exception {
-        final List<Event> events = getFillEventHistory(expectedSize).getEvents();
-        // Validation check
-        if (expectedSize > 0 && events == null || events.size() != expectedSize) {
-            throw new IllegalStateException("INTERNAL ERROR: events should have " + expectedSize
-                    + ", but it is: " + events);
-        }
-        return events;
-    }
-
-    /**
-     * Gets the {@link FillEventHistory}, waiting until it has the expected size.
-     */
-    public static FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
-        final InstrumentedAutoFillService service = peekInstance();
-
-        if (expectedSize == 0) {
-            // Need to always sleep as there is no condition / callback to be used to wait until
-            // expected number of events is set.
-            SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
-            final FillEventHistory history = service.getFillEventHistory();
-            assertThat(history.getEvents()).isNull();
-            return history;
-        }
-
-        return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
-            final FillEventHistory history = service.getFillEventHistory();
-            if (history == null) {
-                return null;
-            }
-            final List<Event> events = history.getEvents();
-            if (events != null) {
-                if (events.size() != expectedSize) {
-                    Log.v(TAG, "Didn't get " + expectedSize + " events yet: " + events);
-                    return null;
-                }
-            } else {
-                Log.v(TAG, "Events is still null (expecting " + expectedSize + ")");
-                return null;
-            }
-            return history;
-        });
-    }
-
-    /**
-     * Asserts there is no {@link FillEventHistory}.
-     */
-    public static void assertNoFillEventHistory() {
-        // Need to always sleep as there is no condition / callback to be used to wait until
-        // expected number of events is set.
-        SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
-        assertThat(peekInstance().getFillEventHistory()).isNull();
-
-    }
-
-    /**
-     * Gets the service label associated with the current instance.
-     */
-    public static String getServiceLabel() {
-        return sServiceLabel;
-    }
-
-    private void handleConnected(boolean connected) {
-        Log.v(TAG, "handleConnected(): from " + sConnected.get() + " to " + connected);
-        sConnected.set(connected);
-    }
-
-    @Override
-    public void onConnected() {
-        Log.v(TAG, "onConnected");
-        if (mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
-            dumpSelf();
-            sReplier.addException(new IllegalStateException("onConnected() called again"));
-        }
-        mConnected = true;
-        mHandler.post(() -> handleConnected(true));
-    }
-
-    @Override
-    public void onDisconnected() {
-        Log.v(TAG, "onDisconnected");
-        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
-            dumpSelf();
-            sReplier.addException(
-                    new IllegalStateException("onDisconnected() called when disconnected"));
-        }
-        mConnected = false;
-        mHandler.post(() -> handleConnected(false));
-    }
-
-    @Override
-    public void onFillRequest(android.service.autofill.FillRequest request,
-            CancellationSignal cancellationSignal, FillCallback callback) {
-        final ComponentName component = getLastActivityComponent(request.getFillContexts());
-        if (DUMP_FILL_REQUESTS) {
-            dumpStructure("onFillRequest()", request.getFillContexts());
-        } else {
-            Log.i(TAG, "onFillRequest() for " + component.toShortString());
-        }
-        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
-            dumpSelf();
-            sReplier.addException(
-                    new IllegalStateException("onFillRequest() called when disconnected"));
-        }
-
-        if (!TestNameUtils.isRunningTest()) {
-            Log.e(TAG, "onFillRequest(" + component + ") called after tests finished");
-            return;
-        }
-        if (!fromSamePackage(component))  {
-            Log.w(TAG, "Ignoring onFillRequest() from different package: " + component);
-            return;
-        }
-        mHandler.post(
-                () -> sReplier.onFillRequest(request.getFillContexts(), request.getClientState(),
-                        cancellationSignal, callback, request.getFlags(),
-                        request.getInlineSuggestionsRequest(), request.getId()));
-    }
-
-    @Override
-    public void onSaveRequest(android.service.autofill.SaveRequest request,
-            SaveCallback callback) {
-        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
-            dumpSelf();
-            sReplier.addException(
-                    new IllegalStateException("onSaveRequest() called when disconnected"));
-        }
-        mHandler.post(()->handleSaveRequest(request, callback));
-    }
-
-    private void handleSaveRequest(android.service.autofill.SaveRequest request,
-            SaveCallback callback) {
-        final ComponentName component = getLastActivityComponent(request.getFillContexts());
-        if (!TestNameUtils.isRunningTest()) {
-            Log.e(TAG, "onSaveRequest(" + component + ") called after tests finished");
-            return;
-        }
-        if (!fromSamePackage(component)) {
-            Log.w(TAG, "Ignoring onSaveRequest() from different package: " + component);
-            return;
-        }
-        if (DUMP_SAVE_REQUESTS) {
-            dumpStructure("onSaveRequest()", request.getFillContexts());
-        } else {
-            Log.i(TAG, "onSaveRequest() for " + component.toShortString());
-        }
-        mHandler.post(() -> sReplier.onSaveRequest(request.getFillContexts(),
-                request.getClientState(), callback,
-                request.getDatasetIds()));
-    }
-
-    public static boolean isConnected() {
-        return sConnected.get();
-    }
-
-    private boolean fromSamePackage(ComponentName component) {
-        final String actualPackage = component.getPackageName();
-        if (!actualPackage.equals(getPackageName())
-                && !actualPackage.equals(sReplier.mAcceptedPackageName)) {
-            Log.w(TAG, "Got request from package " + actualPackage);
-            return false;
-        }
-        return true;
-    }
-
-    private ComponentName getLastActivityComponent(List<FillContext> contexts) {
-        return contexts.get(contexts.size() - 1).getStructure().getActivityComponent();
-    }
-
-    private void dumpSelf()  {
-        try {
-            try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
-                dump(null, pw, null);
-                pw.flush();
-                final String dump = sw.toString();
-                Log.e(TAG, "dumpSelf(): " + dump);
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "I don't always fail to dump, but when I do, I dump the failure", e);
-        }
-    }
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.print("sConnected: "); pw.println(sConnected);
-        pw.print("mConnected: "); pw.println(mConnected);
-        pw.print("sInstance: "); pw.println(sInstance);
-        pw.println("sReplier: "); sReplier.dump(pw);
-    }
-
-    /**
-     * Waits until {@link #onConnected()} is called, or fails if it times out.
-     *
-     * <p>This method is useful on tests that explicitly verifies the connection, but should be
-     * avoided in other tests, as it adds extra time to the test execution (and flakiness in cases
-     * where the service might have being disconnected already; for example, if the fill request
-     * was replied with a {@code null} response) - if a text needs to block until the service
-     * receives a callback, it should use {@link Replier#getNextFillRequest()} instead.
-     */
-    public static void waitUntilConnected() throws Exception {
-        waitConnectionState(CONNECTION_TIMEOUT, true);
-    }
-
-    /**
-     * Waits until {@link #onDisconnected()} is called, or fails if it times out.
-     *
-     * <p>This method is useful on tests that explicitly verifies the connection, but should be
-     * avoided in other tests, as it adds extra time to the test execution.
-     */
-    public static void waitUntilDisconnected() throws Exception {
-        waitConnectionState(IDLE_UNBIND_TIMEOUT, false);
-    }
-
-    private static void waitConnectionState(Timeout timeout, boolean expected) throws Exception {
-        timeout.run("wait for connected=" + expected,  () -> {
-            return isConnected() == expected ? Boolean.TRUE : null;
-        });
-    }
-
-    /**
-     * Gets the {@link Replier} singleton.
-     */
-    static Replier getReplier() {
-        return sReplier;
-    }
-
-    static void resetStaticState() {
-        sInstance.set(null);
-        sConnected.set(false);
-        sServiceLabel = SERVICE_CLASS;
-    }
-
-    /**
-     * POJO representation of the contents of a
-     * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
-     * CancellationSignal, FillCallback)} that can be asserted at the end of a test case.
-     */
-    public static final class FillRequest {
-        public final AssistStructure structure;
-        public final List<FillContext> contexts;
-        public final Bundle data;
-        public final CancellationSignal cancellationSignal;
-        public final FillCallback callback;
-        public final int flags;
-        public final InlineSuggestionsRequest inlineRequest;
-
-        private FillRequest(List<FillContext> contexts, Bundle data,
-                CancellationSignal cancellationSignal, FillCallback callback, int flags,
-                InlineSuggestionsRequest inlineRequest) {
-            this.contexts = contexts;
-            this.data = data;
-            this.cancellationSignal = cancellationSignal;
-            this.callback = callback;
-            this.flags = flags;
-            this.structure = contexts.get(contexts.size() - 1).getStructure();
-            this.inlineRequest = inlineRequest;
-        }
-
-        @Override
-        public String toString() {
-            return "FillRequest[activity=" + getActivityName(contexts) + ", flags=" + flags
-                    + ", bundle=" + data + ", structure=" + Helper.toString(structure) + "]";
-        }
-    }
-
-    /**
-     * POJO representation of the contents of a
-     * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)}
-     * that can be asserted at the end of a test case.
-     */
-    public static final class SaveRequest {
-        public final List<FillContext> contexts;
-        public final AssistStructure structure;
-        public final Bundle data;
-        public final SaveCallback callback;
-        public final List<String> datasetIds;
-
-        private SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
-                List<String> datasetIds) {
-            if (contexts != null && contexts.size() > 0) {
-                structure = contexts.get(contexts.size() - 1).getStructure();
-            } else {
-                structure = null;
-            }
-            this.contexts = contexts;
-            this.data = data;
-            this.callback = callback;
-            this.datasetIds = datasetIds;
-        }
-
-        @Override
-        public String toString() {
-            return "SaveRequest:" + getActivityName(contexts);
-        }
-    }
-
-    /**
-     * Object used to answer a
-     * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
-     * CancellationSignal, FillCallback)}
-     * on behalf of a unit test method.
-     */
-    public static final class Replier {
-
-        private final BlockingQueue<CannedFillResponse> mResponses = new LinkedBlockingQueue<>();
-        private final BlockingQueue<FillRequest> mFillRequests = new LinkedBlockingQueue<>();
-        private final BlockingQueue<SaveRequest> mSaveRequests = new LinkedBlockingQueue<>();
-
-        private List<Throwable> mExceptions;
-        private IntentSender mOnSaveIntentSender;
-        private String mAcceptedPackageName;
-
-        private Handler mHandler;
-
-        private boolean mReportUnhandledFillRequest = true;
-        private boolean mReportUnhandledSaveRequest = true;
-
-        private Replier() {
-        }
-
-        private IdMode mIdMode = IdMode.RESOURCE_ID;
-
-        public void setIdMode(IdMode mode) {
-            this.mIdMode = mode;
-        }
-
-        public void acceptRequestsFromPackage(String packageName) {
-            mAcceptedPackageName = packageName;
-        }
-
-        /**
-         * Gets the exceptions thrown asynchronously, if any.
-         */
-        @Nullable
-        public List<Throwable> getExceptions() {
-            return mExceptions;
-        }
-
-        private void addException(@Nullable Throwable e) {
-            if (e == null) return;
-
-            if (mExceptions == null) {
-                mExceptions = new ArrayList<>();
-            }
-            mExceptions.add(e);
-        }
-
-        /**
-         * Sets the expectation for the next {@code onFillRequest} as {@link FillResponse} with just
-         * one {@link Dataset}.
-         */
-        public Replier addResponse(CannedDataset dataset) {
-            return addResponse(new CannedFillResponse.Builder()
-                    .addDataset(dataset)
-                    .build());
-        }
-
-        /**
-         * Sets the expectation for the next {@code onFillRequest}.
-         */
-        public Replier addResponse(CannedFillResponse response) {
-            if (response == null) {
-                throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
-            }
-            mResponses.add(response);
-            return this;
-        }
-
-        /**
-         * Sets the {@link IntentSender} that is passed to
-         * {@link SaveCallback#onSuccess(IntentSender)}.
-         */
-        public Replier setOnSave(IntentSender intentSender) {
-            mOnSaveIntentSender = intentSender;
-            return this;
-        }
-
-        /**
-         * Gets the next fill request, in the order received.
-         */
-        public FillRequest getNextFillRequest() {
-            FillRequest request;
-            try {
-                request = mFillRequests.poll(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                throw new IllegalStateException("Interrupted", e);
-            }
-            if (request == null) {
-                throw new RetryableException(FILL_TIMEOUT, "onFillRequest() not called");
-            }
-            return request;
-        }
-
-        /**
-         * Asserts that {@link #onFillRequest(List, Bundle, CancellationSignal, FillCallback, int)}
-         * was not called.
-         *
-         * <p>Should only be called in cases where it's not expected to be called, as it will
-         * sleep for a few ms.
-         */
-        public void assertOnFillRequestNotCalled() {
-            SystemClock.sleep(FILL_TIMEOUT.getMaxValue());
-            assertThat(mFillRequests).isEmpty();
-        }
-
-        /**
-         * Asserts all {@link AutofillService#onFillRequest(
-         * android.service.autofill.FillRequest,  CancellationSignal, FillCallback) fill requests}
-         * received by the service were properly {@link #getNextFillRequest() handled} by the test
-         * case.
-         */
-        public void assertNoUnhandledFillRequests() {
-            if (mFillRequests.isEmpty()) return; // Good job, test case!
-
-            if (!mReportUnhandledFillRequest) {
-                // Just log, so it's not thrown again on @After if already thrown on main body
-                Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
-                        + "but logging just in case: " + mFillRequests);
-                return;
-            }
-
-            mReportUnhandledFillRequest = false;
-            throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
-                    + mFillRequests);
-        }
-
-        /**
-         * Gets the current number of unhandled requests.
-         */
-        public int getNumberUnhandledFillRequests() {
-            return mFillRequests.size();
-        }
-
-        /**
-         * Gets the next save request, in the order received.
-         *
-         * <p>Typically called at the end of a test case, to assert the initial request.
-         */
-        public SaveRequest getNextSaveRequest() {
-            SaveRequest request;
-            try {
-                request = mSaveRequests.poll(SAVE_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                throw new IllegalStateException("Interrupted", e);
-            }
-            if (request == null) {
-                throw new RetryableException(SAVE_TIMEOUT, "onSaveRequest() not called");
-            }
-            return request;
-        }
-
-        /**
-         * Asserts all
-         * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)
-         * save requests} received by the service were properly
-         * {@link #getNextFillRequest() handled} by the test case.
-         */
-        public void assertNoUnhandledSaveRequests() {
-            if (mSaveRequests.isEmpty()) return; // Good job, test case!
-
-            if (!mReportUnhandledSaveRequest) {
-                // Just log, so it's not thrown again on @After if already thrown on main body
-                Log.d(TAG, "assertNoUnhandledSaveRequests(): already reported, "
-                        + "but logging just in case: " + mSaveRequests);
-                return;
-            }
-
-            mReportUnhandledSaveRequest = false;
-            throw new AssertionError(mSaveRequests.size() + " unhandled save requests: "
-                    + mSaveRequests);
-        }
-
-        public void setHandler(Handler handler) {
-            mHandler = handler;
-        }
-
-        /**
-         * Resets its internal state.
-         */
-        public void reset() {
-            mResponses.clear();
-            mFillRequests.clear();
-            mSaveRequests.clear();
-            mExceptions = null;
-            mOnSaveIntentSender = null;
-            mAcceptedPackageName = null;
-            mReportUnhandledFillRequest = true;
-            mReportUnhandledSaveRequest = true;
-        }
-
-        private void onFillRequest(List<FillContext> contexts, Bundle data,
-                CancellationSignal cancellationSignal, FillCallback callback, int flags,
-                InlineSuggestionsRequest inlineRequest, int requestId) {
-            try {
-                CannedFillResponse response = null;
-                try {
-                    response = mResponses.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-                } catch (InterruptedException e) {
-                    Log.w(TAG, "Interrupted getting CannedResponse: " + e);
-                    Thread.currentThread().interrupt();
-                    addException(e);
-                    return;
-                }
-                if (response == null) {
-                    final String activityName = getActivityName(contexts);
-                    final String msg = "onFillRequest() for activity " + activityName
-                            + " received when no canned response was set.";
-                    dumpStructure(msg, contexts);
-                    return;
-                }
-                if (response.getResponseType() == NULL) {
-                    Log.d(TAG, "onFillRequest(): replying with null");
-                    callback.onSuccess(null);
-                    return;
-                }
-
-                if (response.getResponseType() == TIMEOUT) {
-                    Log.d(TAG, "onFillRequest(): not replying at all");
-                    return;
-                }
-
-                if (response.getResponseType() == FAILURE) {
-                    Log.d(TAG, "onFillRequest(): replying with failure");
-                    callback.onFailure("D'OH!");
-                    return;
-                }
-
-                if (response.getResponseType() == ResponseType.NO_MORE) {
-                    Log.w(TAG, "onFillRequest(): replying with null when not expecting more");
-                    addException(new IllegalStateException("got unexpected request"));
-                    callback.onSuccess(null);
-                    return;
-                }
-
-                final String failureMessage = response.getFailureMessage();
-                if (failureMessage != null) {
-                    Log.v(TAG, "onFillRequest(): failureMessage = " + failureMessage);
-                    callback.onFailure(failureMessage);
-                    return;
-                }
-
-                final FillResponse fillResponse;
-
-                switch (mIdMode) {
-                    case RESOURCE_ID:
-                        fillResponse = response.asFillResponse(contexts,
-                                (id) -> Helper.findNodeByResourceId(contexts, id));
-                        break;
-                    case HTML_NAME:
-                        fillResponse = response.asFillResponse(contexts,
-                                (name) -> Helper.findNodeByHtmlName(contexts, name));
-                        break;
-                    case HTML_NAME_OR_RESOURCE_ID:
-                        fillResponse = response.asFillResponse(contexts,
-                                (id) -> Helper.findNodeByHtmlNameOrResourceId(contexts, id));
-                        break;
-                    default:
-                        throw new IllegalStateException("Unknown id mode: " + mIdMode);
-                }
-
-                if (response.getResponseType() == ResponseType.DELAY) {
-                    mHandler.postDelayed(() -> {
-                        Log.v(TAG,
-                                "onFillRequest(" + requestId + "): fillResponse = " + fillResponse);
-                        callback.onSuccess(fillResponse);
-                        // Add a fill request to let test case know response was sent.
-                        Helper.offer(mFillRequests,
-                                new FillRequest(contexts, data, cancellationSignal, callback,
-                                        flags, inlineRequest), CONNECTION_TIMEOUT.ms());
-                    }, RESPONSE_DELAY_MS);
-                } else {
-                    Log.v(TAG, "onFillRequest(" + requestId + "): fillResponse = " + fillResponse);
-                    callback.onSuccess(fillResponse);
-                }
-            } catch (Throwable t) {
-                addException(t);
-            } finally {
-                Helper.offer(mFillRequests, new FillRequest(contexts, data, cancellationSignal,
-                        callback, flags, inlineRequest), CONNECTION_TIMEOUT.ms());
-            }
-        }
-
-        private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
-                List<String> datasetIds) {
-            Log.d(TAG, "onSaveRequest(): sender=" + mOnSaveIntentSender);
-
-            try {
-                if (mOnSaveIntentSender != null) {
-                    callback.onSuccess(mOnSaveIntentSender);
-                } else {
-                    callback.onSuccess();
-                }
-            } finally {
-                Helper.offer(mSaveRequests, new SaveRequest(contexts, data, callback, datasetIds),
-                        CONNECTION_TIMEOUT.ms());
-            }
-        }
-
-        private void dump(PrintWriter pw) {
-            pw.print("mResponses: "); pw.println(mResponses);
-            pw.print("mFillRequests: "); pw.println(mFillRequests);
-            pw.print("mSaveRequests: "); pw.println(mSaveRequests);
-            pw.print("mExceptions: "); pw.println(mExceptions);
-            pw.print("mOnSaveIntentSender: "); pw.println(mOnSaveIntentSender);
-            pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
-            pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
-            pw.print("mReportUnhandledFillRequest: "); pw.println(mReportUnhandledSaveRequest);
-            pw.print("mIdMode: "); pw.println(mIdMode);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillServiceCompatMode.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillServiceCompatMode.java
deleted file mode 100644
index b1c2a45..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillServiceCompatMode.java
+++ /dev/null
@@ -1,36 +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.autofillservice.cts;
-import android.service.autofill.AutofillService;
-
-/**
- * Implementation of {@link AutofillService} using A11Y compat mode used in the tests.
- */
-public class InstrumentedAutoFillServiceCompatMode extends InstrumentedAutoFillService {
-
-    @SuppressWarnings("hiding")
-    static final String SERVICE_PACKAGE = "android.autofillservice.cts";
-    @SuppressWarnings("hiding")
-    static final String SERVICE_CLASS = "InstrumentedAutoFillServiceCompatMode";
-    @SuppressWarnings("hiding")
-    static final String SERVICE_NAME = SERVICE_PACKAGE + "/." + SERVICE_CLASS;
-
-    public InstrumentedAutoFillServiceCompatMode() {
-        sInstance.set(this);
-        sServiceLabel = SERVICE_CLASS;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
deleted file mode 100644
index c7c5070..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity that has the following fields:
- *
- * <ul>
- *   <li>Username EditText (id: username, no input-type)
- *   <li>Password EditText (id: "username", input-type textPassword)
- *   <li>Clear Button
- *   <li>Save Button
- *   <li>Login Button
- * </ul>
- */
-public class LoginActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "LoginActivity";
-    private static String WELCOME_TEMPLATE = "Welcome to the new activity, %s!";
-    private static final long LOGIN_TIMEOUT_MS = 1000;
-
-    public static final String ID_USERNAME_CONTAINER = "username_container";
-    public static final String AUTHENTICATION_MESSAGE = "Authentication failed. D'OH!";
-    public static final String BACKDOOR_USERNAME = "LemmeIn";
-    public static final String BACKDOOR_PASSWORD_SUBSTRING = "pass";
-
-    private static LoginActivity sCurrentActivity;
-
-    private LinearLayout mUsernameContainer;
-    private TextView mUsernameLabel;
-    private EditText mUsernameEditText;
-    private TextView mPasswordLabel;
-    private EditText mPasswordEditText;
-    private TextView mOutput;
-    private Button mLoginButton;
-    private Button mSaveButton;
-    private Button mCancelButton;
-    private Button mClearButton;
-    private FillExpectation mExpectation;
-
-    // State used to synchronously get the result of a login attempt.
-    private CountDownLatch mLoginLatch;
-    private String mLoginMessage;
-
-    /**
-     * Gets the expected welcome message for a given username.
-     */
-    public static String getWelcomeMessage(String username) {
-        return String.format(WELCOME_TEMPLATE,  username);
-    }
-
-    /**
-     * Gests the latest instance.
-     *
-     * <p>Typically used in test cases that rotates the activity
-     */
-    @SuppressWarnings("unchecked") // Its up to caller to make sure it's setting the right one
-    public static <T extends LoginActivity> T getCurrentActivity() {
-        return (T) sCurrentActivity;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(getContentView());
-
-        mUsernameContainer = findViewById(R.id.username_container);
-        mLoginButton = findViewById(R.id.login);
-        mSaveButton = findViewById(R.id.save);
-        mClearButton = findViewById(R.id.clear);
-        mCancelButton = findViewById(R.id.cancel);
-        mUsernameLabel = findViewById(R.id.username_label);
-        mUsernameEditText = findViewById(R.id.username);
-        mPasswordLabel = findViewById(R.id.password_label);
-        mPasswordEditText = findViewById(R.id.password);
-        mOutput = findViewById(R.id.output);
-
-        mLoginButton.setOnClickListener((v) -> login());
-        mSaveButton.setOnClickListener((v) -> save());
-        mClearButton.setOnClickListener((v) -> {
-            mUsernameEditText.setText("");
-            mPasswordEditText.setText("");
-            mOutput.setText("");
-            getAutofillManager().cancel();
-        });
-        mCancelButton.setOnClickListener((OnClickListener) v -> finish());
-
-        sCurrentActivity = this;
-    }
-
-    protected int getContentView() {
-        return R.layout.login_activity;
-    }
-
-    /**
-     * Emulates a login action.
-     */
-    private void login() {
-        final String username = mUsernameEditText.getText().toString();
-        final String password = mPasswordEditText.getText().toString();
-        final boolean valid = username.equals(password)
-                || (TextUtils.isEmpty(username) && TextUtils.isEmpty(password))
-                || password.contains(BACKDOOR_PASSWORD_SUBSTRING)
-                || username.equals(BACKDOOR_USERNAME);
-
-        if (valid) {
-            Log.d(TAG, "login ok: " + username);
-            final Intent intent = new Intent(this, WelcomeActivity.class);
-            final String message = getWelcomeMessage(username);
-            intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, message);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            setLoginMessage(message);
-            startActivity(intent);
-            finish();
-        } else {
-            Log.d(TAG, "login failed: " + AUTHENTICATION_MESSAGE);
-            mOutput.setText(AUTHENTICATION_MESSAGE);
-            setLoginMessage(AUTHENTICATION_MESSAGE);
-        }
-    }
-
-    private void setLoginMessage(String message) {
-        Log.d(TAG, "setLoginMessage(): " + message);
-        if (mLoginLatch != null) {
-            mLoginMessage = message;
-            mLoginLatch.countDown();
-        }
-    }
-
-    /**
-     * Explicitly forces the AutofillManager to save the username and password.
-     */
-    private void save() {
-        final InputMethodManager imm = (InputMethodManager) getSystemService(
-                Context.INPUT_METHOD_SERVICE);
-        imm.hideSoftInputFromWindow(mUsernameEditText.getWindowToken(), 0);
-        getAutofillManager().commit();
-    }
-
-    /**
-     * Sets the expectation for an autofill request (for all fields), so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    public void expectAutoFill(String username, String password) {
-        mExpectation = new FillExpectation(username, password);
-        mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher);
-        mPasswordEditText.addTextChangedListener(mExpectation.ccPasswordWatcher);
-    }
-
-    /**
-     * Sets the expectation for an autofill request (for username only), so it can be asserted
-     * through {@link #assertAutoFilled()} later.
-     *
-     * <p><strong>NOTE: </strong>This method checks the result of text change, it should not call
-     * this method too early, it may cause test fail. Call this method before checking autofill
-     * behavior.
-     * <pre>
-     * An example usage is:
-     * <code>
-     *  public void testAutofill() throws Exception {
-     *      // Enable service and trigger autofill
-     *      enableService();
-     *      final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-     *                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
-     *                         .setField(ID_USERNAME, "test")
-     *                         .setField(ID_PASSWORD, "tweet")
-     *                         .setPresentation(createPresentation("Second Dude"))
-     *                         .setInlinePresentation(createInlinePresentation("Second Dude"))
-     *                         .build());
-     *      sReplier.addResponse(builder.build());
-     *      mUiBot.selectByRelativeId(ID_USERNAME);
-     *      sReplier.getNextFillRequest();
-     *      // Filter suggestion
-     *      mActivity.onUsername((v) -> v.setText("t"));
-     *      mUiBot.assertDatasets("Second Dude");
-     *
-     *      // Call expectAutoFill() before checking autofill behavior
-     *      mActivity.expectAutoFill("test", "tweet");
-     *      mUiBot.selectDataset("Second Dude");
-     *      mActivity.assertAutoFilled();
-     *  }
-     * </code>
-     * </pre>
-     */
-    public void expectAutoFill(String username) {
-        mExpectation = new FillExpectation(username);
-        mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher);
-    }
-
-    /**
-     * Sets the expectation for an autofill request (for password only), so it can be asserted
-     * through {@link #assertAutoFilled()} later.
-     *
-     * <p><strong>NOTE: </strong>This method checks the result of text change, it should not call
-     * this method too early, it may cause test fail. Call this method before checking autofill
-     * behavior. {@See #expectAutoFill(String)} for how it should be used.
-     */
-    public void expectPasswordAutoFill(String password) {
-        mExpectation = new FillExpectation(null, password);
-        mPasswordEditText.addTextChangedListener(mExpectation.ccPasswordWatcher);
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, String)}.
-     */
-    public void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        if (mExpectation.ccUsernameWatcher != null) {
-            mExpectation.ccUsernameWatcher.assertAutoFilled();
-        }
-        if (mExpectation.ccPasswordWatcher != null) {
-            mExpectation.ccPasswordWatcher.assertAutoFilled();
-        }
-    }
-
-    public void forceAutofillOnUsername() {
-        syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mUsernameEditText));
-    }
-
-    public void forceAutofillOnPassword() {
-        syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mPasswordEditText));
-    }
-
-    /**
-     * Visits the {@code username_label} in the UiThread.
-     */
-    public void onUsernameLabel(Visitor<TextView> v) {
-        syncRunOnUiThread(() -> v.visit(mUsernameLabel));
-    }
-
-    /**
-     * Visits the {@code username} in the UiThread.
-     */
-    public void onUsername(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mUsernameEditText));
-    }
-
-    @Override
-    public void clearFocus() {
-        syncRunOnUiThread(() -> ((View) mUsernameContainer.getParent()).requestFocus());
-    }
-
-    /**
-     * Gets the {@code username_label} view.
-     */
-    public TextView getUsernameLabel() {
-        return mUsernameLabel;
-    }
-
-    /**
-     * Gets the {@code username} view.
-     */
-    public EditText getUsername() {
-        return mUsernameEditText;
-    }
-
-    /**
-     * Visits the {@code password_label} in the UiThread.
-     */
-    public void onPasswordLabel(Visitor<TextView> v) {
-        syncRunOnUiThread(() -> v.visit(mPasswordLabel));
-    }
-
-    /**
-     * Visits the {@code password} in the UiThread.
-     */
-    public void onPassword(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mPasswordEditText));
-    }
-
-    /**
-     * Visits the {@code login} button in the UiThread.
-     */
-    public void onLogin(Visitor<Button> v) {
-        syncRunOnUiThread(() -> v.visit(mLoginButton));
-    }
-
-    /**
-     * Gets the {@code password} view.
-     */
-    public EditText getPassword() {
-        return mPasswordEditText;
-    }
-
-    /**
-     * Taps the login button in the UI thread.
-     */
-    public String tapLogin() throws Exception {
-        mLoginLatch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> mLoginButton.performClick());
-        boolean called = mLoginLatch.await(LOGIN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) waiting for login", LOGIN_TIMEOUT_MS)
-                .that(called).isTrue();
-        return mLoginMessage;
-    }
-
-    /**
-     * Taps the save button in the UI thread.
-     */
-    public void tapSave() throws Exception {
-        syncRunOnUiThread(() -> mSaveButton.performClick());
-    }
-
-    /**
-     * Taps the clear button in the UI thread.
-     */
-    public void tapClear() {
-        syncRunOnUiThread(() -> mClearButton.performClick());
-    }
-
-    /**
-     * Sets the window flags.
-     */
-    public void setFlags(int flags) {
-        Log.d(TAG, "setFlags():" + flags);
-        syncRunOnUiThread(() -> getWindow().setFlags(flags, flags));
-    }
-
-    /**
-     * Adds a child view to the root container.
-     */
-    public void addChild(View child) {
-        Log.d(TAG, "addChild(" + child + "): id=" + child.getAutofillId());
-        final ViewGroup root = (ViewGroup) mUsernameContainer.getParent();
-        syncRunOnUiThread(() -> root.addView(child));
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final OneTimeTextWatcher ccUsernameWatcher;
-        private final OneTimeTextWatcher ccPasswordWatcher;
-
-        private FillExpectation(String username, String password) {
-            ccUsernameWatcher = username == null ? null
-                    : new OneTimeTextWatcher("username", mUsernameEditText, username);
-            ccPasswordWatcher = password == null ? null
-                    : new OneTimeTextWatcher("password", mPasswordEditText, password);
-        }
-
-        private FillExpectation(String username) {
-            this(username, null);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java
deleted file mode 100644
index 1bad60a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.NO_MOAR_RESPONSES;
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.inline.InlineLoginActivityTest;
-import android.service.autofill.FillContext;
-import android.view.View;
-
-import org.junit.Test;
-
-/**
- * This is the common test cases with {@link LoginActivityTest} and {@link InlineLoginActivityTest}.
- */
-public abstract class LoginActivityCommonTestCase extends AbstractLoginActivityTestCase {
-
-    protected LoginActivityCommonTestCase() {}
-
-    protected LoginActivityCommonTestCase(UiBot inlineUiBot) {
-        super(inlineUiBot);
-    }
-
-    @Test
-    public void testAutoFillNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger autofill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-
-        // Make sure a fill request is called but don't check for connected() - as we're returning
-        // a null response, the service might have been disconnected already by the time we assert
-        // it.
-        sReplier.getNextFillRequest();
-
-        // Make sure UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Test connection lifecycle.
-        waitUntilDisconnected();
-    }
-
-    @Test
-    public void testAutoFillNoDatasets_multipleFields_alwaysNull() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE)
-                .addResponse(NO_MOAR_RESPONSES);
-
-        // Trigger autofill
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Tap back and forth to make sure no more requests are shown
-
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.assertNoDatasetsEver();
-
-        mActivity.onUsername(View::requestFocus);
-        mUiBot.assertNoDatasetsEver();
-
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.assertNoDatasetsEver();
-    }
-
-
-    @Test
-    public void testAutofill_oneDataset() throws Exception {
-        testBasicLoginAutofill(/* numDatasets= */ 1, /* selectedDatasetIndex= */ 0);
-    }
-
-    @Test
-    public void testAutofill_twoDatasets_selectFirstDataset() throws Exception {
-        testBasicLoginAutofill(/* numDatasets= */ 2, /* selectedDatasetIndex= */ 0);
-
-    }
-
-    @Test
-    public void testAutofill_twoDatasets_selectSecondDataset() throws Exception {
-        testBasicLoginAutofill(/* numDatasets= */ 2, /* selectedDatasetIndex= */ 1);
-    }
-
-    private void testBasicLoginAutofill(int numDatasets, int selectedDatasetIndex)
-            throws Exception {
-        // Set service.
-        enableService();
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final View username = mActivity.getUsername();
-        final View password = mActivity.getPassword();
-
-        String[] expectedDatasets = new String[numDatasets];
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder();
-        for (int i = 0; i < numDatasets; i++) {
-            builder.addDataset(new CannedFillResponse.CannedDataset.Builder()
-                    .setField(ID_USERNAME, "dude" + i)
-                    .setField(ID_PASSWORD, "sweet" + i)
-                    .setPresentation("The Dude" + i, isInlineMode())
-                    .build());
-            expectedDatasets[i] = "The Dude" + i;
-        }
-
-        sReplier.addResponse(builder.build());
-        mActivity.expectAutoFill("dude" + selectedDatasetIndex, "sweet" + selectedDatasetIndex);
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-
-        mUiBot.assertDatasets(expectedDatasets);
-        callback.assertUiShownEvent(username);
-
-        mUiBot.selectDataset(expectedDatasets[selectedDatasetIndex]);
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        callback.assertUiHiddenEvent(username);
-
-        // Make sure input was sanitized.
-        final InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
-        assertWithMessage("CancelationSignal is null").that(request.cancellationSignal).isNotNull();
-        assertTextIsSanitized(request.structure, ID_PASSWORD);
-        final FillContext fillContext = request.contexts.get(request.contexts.size() - 1);
-        assertThat(fillContext.getFocusedId())
-                .isEqualTo(findAutofillIdByResourceId(fillContext, ID_USERNAME));
-        if (isInlineMode()) {
-            assertThat(request.inlineRequest).isNotNull();
-        } else {
-            assertThat(request.inlineRequest).isNull();
-        }
-
-        // Make sure initial focus was properly set.
-        assertWithMessage("Username node is not focused").that(
-                findNodeByResourceId(request.structure, ID_USERNAME).isFocused()).isTrue();
-        assertWithMessage("Password node is focused").that(
-                findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
-    }
-
-    @Test
-    public void testClearFocusBeforeRespond() throws Exception {
-        // Set service
-        enableService();
-
-        // Trigger auto-fill
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-
-        // Clear focus before responded
-        mActivity.onUsername(View::clearFocus);
-        mUiBot.waitForIdleSync();
-
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setPresentation("The Dude", isInlineMode())
-                        .build());
-        sReplier.addResponse(builder.build());
-        sReplier.getNextFillRequest();
-
-        // Confirm no datasets shown
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    @Test
-    public void testSwitchFocusBeforeResponse() throws Exception {
-        // Set service
-        enableService();
-
-        // Trigger auto-fill
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-
-        // Trigger second fill request
-        mUiBot.selectByRelativeId(ID_PASSWORD);
-        mUiBot.waitForIdleSync();
-
-        // Respond for username
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setPresentation("The Dude", isInlineMode())
-                        .build())
-                .build());
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Set expectations and respond for password
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation("The Password", isInlineMode())
-                        .build())
-                .build());
-        sReplier.getNextFillRequest();
-
-        // confirm second response shown
-        mUiBot.assertDatasets("The Password");
-    }
-
-    @Test
-    public void testManualRequestWhileFirstResponseDelayed() throws Exception {
-        // Set service
-        enableService();
-
-        // Trigger auto-fill
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-
-        // Trigger second fill request
-        mActivity.forceAutofillOnUsername();
-        mUiBot.waitForIdleSync();
-
-        // Respond for first request
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setPresentation("The Dude", isInlineMode())
-                        .build())
-                .build());
-        sReplier.getNextFillRequest();
-
-        // Set expectations and respond for second request
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude2")
-                        .setPresentation("The Dude 2", isInlineMode())
-                        .build()).build());
-        sReplier.getNextFillRequest();
-
-        // confirm second response shown
-        mUiBot.assertDatasets("The Dude 2");
-    }
-
-    @Test
-    public void testResponseFirstAfterResponseSecond() throws Exception {
-        // Set service
-        enableService();
-
-        // Trigger auto-fill
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-
-        // Trigger second fill request
-        mActivity.forceAutofillOnUsername();
-        mUiBot.waitForIdleSync();
-
-        // Respond for first request
-        sReplier.addResponse(new CannedFillResponse.Builder(CannedFillResponse.ResponseType.DELAY)
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setPresentation("The Dude", isInlineMode())
-                        .build())
-                .build());
-        sReplier.getNextFillRequest();
-
-        // Set expectations and respond for second request
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude2")
-                        .setPresentation("The Dude 2", isInlineMode())
-                        .build()).build());
-        sReplier.getNextFillRequest();
-
-        // confirm second response shown
-        mUiBot.assertDatasets("The Dude 2");
-
-        // Wait first response was sent
-        sReplier.getNextFillRequest();
-
-        // confirm second response still shown
-        mUiBot.assertDatasets("The Dude 2");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
deleted file mode 100644
index ae04ba9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ /dev/null
@@ -1,2907 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
-import static android.autofillservice.cts.CannedFillResponse.FAIL;
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_CANCEL_FILL;
-import static android.autofillservice.cts.Helper.ID_EMPTY;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.allowOverlays;
-import static android.autofillservice.cts.Helper.assertHasFlags;
-import static android.autofillservice.cts.Helper.assertNumberOfChildren;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertTextOnly;
-import static android.autofillservice.cts.Helper.assertValue;
-import static android.autofillservice.cts.Helper.assertViewAutofillState;
-import static android.autofillservice.cts.Helper.disallowOverlays;
-import static android.autofillservice.cts.Helper.dumpStructure;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.isAutofillWindowFullScreen;
-import static android.autofillservice.cts.Helper.setUserComplete;
-import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_CLASS;
-import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_PACKAGE;
-import static android.autofillservice.cts.InstrumentedAutoFillService.isConnected;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
-import static android.autofillservice.cts.LoginActivity.AUTHENTICATION_MESSAGE;
-import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
-import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.content.Context.CLIPBOARD_SERVICE;
-import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-import static android.text.InputType.TYPE_NULL;
-import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
-import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-
-import static com.android.compatibility.common.util.ShellUtils.sendKeyEvent;
-import static com.android.compatibility.common.util.ShellUtils.tap;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.PendingIntent;
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.BroadcastReceiver;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillContext;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.view.View;
-import android.view.View.AccessibilityDelegate;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.autofill.AutofillManager;
-import android.widget.EditText;
-import android.widget.RemoteViews;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * This is the test case covering most scenarios - other test cases will cover characteristics
- * specific to that test's activity (for example, custom views).
- */
-public class LoginActivityTest extends LoginActivityCommonTestCase {
-
-    private static final String TAG = "LoginActivityTest";
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillAutomaticallyAfterServiceReturnedNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Make sure UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Try again, in a field that was added after the first request
-        final EditText child = new EditText(mActivity);
-        child.setId(R.id.empty);
-        mActivity.addChild(child);
-        final OneTimeTextWatcher watcher = new OneTimeTextWatcher("child", child,
-                "new view on the block");
-        child.addTextChangedListener(watcher);
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setField(ID_EMPTY, "new view on the block")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.syncRunOnUiThread(() -> child.requestFocus());
-
-        sReplier.getNextFillRequest();
-
-        // Select the dataset.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        watcher.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillManuallyAfterServiceReturnedNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Make sure UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Try again, forcing it
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-
-        mActivity.forceAutofillOnUsername();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
-
-        // Select the dataset.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillManuallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger autofill.
-        // NOTE: must be on password, as saveOnlyTest() will trigger on username
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Make sure UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-        sReplier.assertNoUnhandledFillRequests();
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.assertNoDatasetsEver();
-        sReplier.assertNoUnhandledFillRequests();
-
-        // Try again, forcing it
-        saveOnlyTest(/* manually= */ true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillAutomaticallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Make sure UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Try again, in a field that was added after the first request
-        final EditText child = new EditText(mActivity);
-        child.setId(R.id.empty);
-        mActivity.addChild(child);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
-                        ID_USERNAME,
-                        ID_PASSWORD,
-                        ID_EMPTY)
-                .build());
-        mActivity.syncRunOnUiThread(() -> child.requestFocus());
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        mActivity.runOnUiThread(() -> child.setText("NOT MR.M"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNoUnhandledSaveRequests();
-        assertThat(saveRequest.datasetIds).isNull();
-
-        // Assert value of expected fields - should not be sanitized.
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(username, "malkovich");
-        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-        assertTextAndValue(password, "malkovich");
-        final ViewNode childNode = findNodeByResourceId(saveRequest.structure, ID_EMPTY);
-        assertTextAndValue(childNode, "NOT MR.M");
-    }
-
-    /**
-     * More detailed test of what should happen after a service returns a {@code null} FillResponse:
-     * views that have already been visit should not trigger a new session, unless a manual autofill
-     * workflow was requested.
-     */
-    @Test
-    @AppModeFull(reason = "testAutoFillNoDatasets() is enough")
-    public void testMultipleIterationsAfterServiceReturnedNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger autofill on username - should call service
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-        waitUntilDisconnected();
-
-        // Every other call should be ignored
-        mActivity.onPassword(View::requestFocus);
-        mActivity.onUsername(View::requestFocus);
-        mActivity.onPassword(View::requestFocus);
-
-        // Trigger autofill by manually requesting username - should call service
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.forceAutofillOnUsername();
-        final FillRequest manualRequest1 = sReplier.getNextFillRequest();
-        assertHasFlags(manualRequest1.flags, FLAG_MANUAL_REQUEST);
-        waitUntilDisconnected();
-
-        // Trigger autofill by manually requesting password - should call service
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.forceAutofillOnPassword();
-        final FillRequest manualRequest2 = sReplier.getNextFillRequest();
-        assertHasFlags(manualRequest2.flags, FLAG_MANUAL_REQUEST);
-        waitUntilDisconnected();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyAlwaysCallServiceAgain() throws Exception {
-        // Set service.
-        enableService();
-
-        // First request
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.onUsername(View::requestFocus);
-        // Waits for the fill request to be sent to the autofill service
-        mUiBot.waitForIdleSync();
-
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-
-        // Second request
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "DUDE")
-                .setField(ID_PASSWORD, "SWEET")
-                .setPresentation(createPresentation("THE DUDE"))
-                .build());
-
-        mActivity.forceAutofillOnUsername();
-        mUiBot.waitForIdleSync();
-
-        final FillRequest secondRequest = sReplier.getNextFillRequest();
-        assertHasFlags(secondRequest.flags, FLAG_MANUAL_REQUEST);
-        mUiBot.assertDatasets("THE DUDE");
-    }
-
-    @Test
-    public void testAutoFillOneDataset() throws Exception {
-        autofillOneDatasetTest(BorderType.NONE);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset_withHeaderAndFooter() is enough")
-    public void testAutoFillOneDataset_withHeader() throws Exception {
-        autofillOneDatasetTest(BorderType.HEADER_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset_withHeaderAndFooter() is enough")
-    public void testAutoFillOneDataset_withFooter() throws Exception {
-        autofillOneDatasetTest(BorderType.FOOTER_ONLY);
-    }
-
-    @Test
-    public void testAutoFillOneDataset_withHeaderAndFooter() throws Exception {
-        autofillOneDatasetTest(BorderType.BOTH);
-    }
-
-    private enum BorderType {
-        NONE,
-        HEADER_ONLY,
-        FOOTER_ONLY,
-        BOTH
-    }
-
-    private void autofillOneDatasetTest(BorderType borderType) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        String expectedHeader = null, expectedFooter = null;
-
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build());
-        if (borderType == BorderType.BOTH || borderType == BorderType.HEADER_ONLY) {
-            expectedHeader = "Head";
-            builder.setHeader(createPresentation(expectedHeader));
-        }
-        if (borderType == BorderType.BOTH || borderType == BorderType.FOOTER_ONLY) {
-            expectedFooter = "Tails";
-            builder.setFooter(createPresentation(expectedFooter));
-        }
-        sReplier.addResponse(builder.build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Dynamically set password to make sure it's sanitized.
-        mActivity.onPassword((v) -> v.setText("I AM GROOT"));
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Auto-fill it.
-        final UiObject2 picker = mUiBot.assertDatasetsWithBorders(expectedHeader, expectedFooter,
-                "The Dude");
-
-        mUiBot.selectDataset(picker, "The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Validation checks.
-
-        // Make sure input was sanitized.
-        final FillRequest request = sReplier.getNextFillRequest();
-        assertWithMessage("CancelationSignal is null").that(request.cancellationSignal).isNotNull();
-        assertTextIsSanitized(request.structure, ID_PASSWORD);
-        final FillContext fillContext = request.contexts.get(request.contexts.size() - 1);
-        assertThat(fillContext.getFocusedId())
-                .isEqualTo(findAutofillIdByResourceId(fillContext, ID_USERNAME));
-
-        // Make sure initial focus was properly set.
-        assertWithMessage("Username node is not focused").that(
-                findNodeByResourceId(request.structure, ID_USERNAME).isFocused()).isTrue();
-        assertWithMessage("Password node is focused").that(
-                findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
-    }
-
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillAgainAfterOnFailure() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(FAIL);
-
-        // Trigger autofill.
-        requestFocusOnUsernameNoWindowChange();
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Try again
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build());
-        sReplier.addResponse(builder.build());
-
-        // Trigger autofill.
-        clearFocus();
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        mActivity.expectAutoFill("dude", "sweet");
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testDatasetPickerPosition() throws Exception {
-        final boolean pickerAndViewBoundsMatches = !isAutofillWindowFullScreen(mContext);
-
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final View username = mActivity.getUsername();
-        final View password = mActivity.getPassword();
-
-        // Set expectations.
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
-                        .setField(ID_PASSWORD, "sweet", createPresentation("SWEET"))
-                        .build());
-        sReplier.addResponse(builder.build());
-
-        // Trigger autofill on username
-        final Rect usernameBoundaries1 = mUiBot.selectByRelativeId(ID_USERNAME).getVisibleBounds();
-        sReplier.getNextFillRequest();
-        callback.assertUiShownEvent(username);
-        final Rect usernamePickerBoundaries1 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
-        Log.v(TAG,
-                "Username1 at " + usernameBoundaries1 + "; picker at " + usernamePickerBoundaries1);
-        // TODO(b/37566627): assertions below might be too aggressive - use range instead?
-        if (pickerAndViewBoundsMatches) {
-            if (usernamePickerBoundaries1.top < usernameBoundaries1.bottom) {
-                assertThat(usernamePickerBoundaries1.bottom).isEqualTo(usernameBoundaries1.top);
-            } else {
-                assertThat(usernamePickerBoundaries1.top).isEqualTo(usernameBoundaries1.bottom);
-            }
-
-            assertThat(usernamePickerBoundaries1.left).isEqualTo(usernameBoundaries1.left);
-        }
-
-        // Move to password
-        final Rect passwordBoundaries1 = mUiBot.selectByRelativeId(ID_PASSWORD).getVisibleBounds();
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-        final Rect passwordPickerBoundaries1 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
-        Log.v(TAG,
-                "Password1 at " + passwordBoundaries1 + "; picker at " + passwordPickerBoundaries1);
-        // TODO(b/37566627): assertions below might be too aggressive - use range instead?
-        if (pickerAndViewBoundsMatches) {
-            if (passwordPickerBoundaries1.top < passwordBoundaries1.bottom) {
-                assertThat(passwordPickerBoundaries1.bottom).isEqualTo(passwordBoundaries1.top);
-            } else {
-                assertThat(passwordPickerBoundaries1.top).isEqualTo(passwordBoundaries1.bottom);
-            }
-            assertThat(passwordPickerBoundaries1.left).isEqualTo(passwordBoundaries1.left);
-        }
-
-        // Then back to username
-        final Rect usernameBoundaries2 = mUiBot.selectByRelativeId(ID_USERNAME).getVisibleBounds();
-        callback.assertUiHiddenEvent(password);
-        callback.assertUiShownEvent(username);
-        final Rect usernamePickerBoundaries2 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
-        Log.v(TAG,
-                "Username2 at " + usernameBoundaries2 + "; picker at " + usernamePickerBoundaries2);
-
-        // And back to the password again..
-        final Rect passwordBoundaries2 = mUiBot.selectByRelativeId(ID_PASSWORD).getVisibleBounds();
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-        final Rect passwordPickerBoundaries2 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
-        Log.v(TAG,
-                "Password2 at " + passwordBoundaries2 + "; picker at " + passwordPickerBoundaries2);
-
-        // Assert final state matches initial...
-        // ... for username
-        assertWithMessage("Username2 at %s; Username1 at %s", usernameBoundaries2,
-                usernamePickerBoundaries1).that(usernameBoundaries2).isEqualTo(usernameBoundaries1);
-        assertWithMessage("Username2 picker at %s; Username1 picker at %s",
-                usernamePickerBoundaries2, usernamePickerBoundaries1).that(
-                usernamePickerBoundaries2).isEqualTo(usernamePickerBoundaries1);
-
-        // ... for password
-        assertWithMessage("Password2 at %s; Password1 at %s", passwordBoundaries2,
-                passwordBoundaries1).that(passwordBoundaries2).isEqualTo(passwordBoundaries1);
-        assertWithMessage("Password2 picker at %s; Password1 picker at %s",
-                passwordPickerBoundaries2, passwordPickerBoundaries1).that(
-                passwordPickerBoundaries2).isEqualTo(passwordPickerBoundaries1);
-
-        // Final validation check
-        callback.assertNumberUnhandledEvents(0);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillTwoDatasetsSameNumberOfFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE")
-                        .setField(ID_PASSWORD, "SWEET")
-                        .setPresentation(createPresentation("THE DUDE"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure all datasets are available...
-        mUiBot.assertDatasets("The Dude", "THE DUDE");
-
-        // ... on all fields.
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("The Dude", "THE DUDE");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillTwoDatasetsUnevenNumberOfFieldsFillsAll() throws Exception {
-        autoFillTwoDatasetsUnevenNumberOfFieldsTest(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillTwoDatasetsUnevenNumberOfFieldsFillsOne() throws Exception {
-        autoFillTwoDatasetsUnevenNumberOfFieldsTest(false);
-    }
-
-    private void autoFillTwoDatasetsUnevenNumberOfFieldsTest(boolean fillsAll) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE")
-                        .setPresentation(createPresentation("THE DUDE"))
-                        .build())
-                .build());
-        if (fillsAll) {
-            mActivity.expectAutoFill("dude", "sweet");
-        } else {
-            mActivity.expectAutoFill("DUDE");
-        }
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure all datasets are available on username...
-        mUiBot.assertDatasets("The Dude", "THE DUDE");
-
-        // ... but just one for password
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("The Dude");
-
-        // Auto-fill it.
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("The Dude", "THE DUDE");
-        if (fillsAll) {
-            mUiBot.selectDataset("The Dude");
-        } else {
-            mUiBot.selectDataset("THE DUDE");
-        }
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillDatasetWithoutFieldIsIgnored() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE")
-                        .setField(ID_PASSWORD, "SWEET")
-                        .build())
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure all datasets are available...
-        mUiBot.assertDatasets("The Dude");
-
-        // ... on all fields.
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("The Dude");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutoFillWhenViewHasChildAccessibilityNodes() throws Exception {
-        mActivity.onUsername((v) -> v.setAccessibilityDelegate(new AccessibilityDelegate() {
-            @Override
-            public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
-                return new AccessibilityNodeProvider() {
-                    @Override
-                    public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
-                        final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
-                        if (virtualViewId == View.NO_ID) {
-                            info.addChild(v, 108);
-                        }
-                        return info;
-                    }
-                };
-            }
-        }));
-
-        testAutoFillOneDataset();
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndMoveFocusAround() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure tapping on other fields from the dataset does not trigger it again
-        requestFocusOnPassword();
-        sReplier.assertNoUnhandledFillRequests();
-
-        requestFocusOnUsername();
-        sReplier.assertNoUnhandledFillRequests();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Make sure tapping on other fields from the dataset does not trigger it again
-        requestFocusOnPassword();
-        mUiBot.assertNoDatasets();
-        requestFocusOnUsernameNoWindowChange();
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    @Test
-    public void testUiNotShownAfterAutofilled() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Make sure tapping on autofilled field does not trigger it again
-        requestFocusOnPassword();
-        mUiBot.assertNoDatasets();
-
-        requestFocusOnUsernameNoWindowChange();
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    @Test
-    public void testAutofillTapOutside() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("The Dude");
-
-        // tapping outside autofill window should close it and raise ui hidden event
-        mUiBot.waitForWindowChange(() -> tap(mActivity.getUsernameLabel()));
-        callback.assertUiHiddenEvent(username);
-
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    public void testAutofillCallbacks() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        final View password = mActivity.getPassword();
-
-        callback.assertUiShownEvent(username);
-
-        requestFocusOnPassword();
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-
-        // Unregister callback to make sure no more events are received
-        mActivity.unregisterCallback();
-        requestFocusOnUsername();
-        // Blindly sleep - we cannot wait on any event as none should have been sent
-        SystemClock.sleep(MyAutofillCallback.MY_TIMEOUT.ms());
-        callback.assertNumberUnhandledEvents(0);
-
-        // Autofill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackDisabled() throws Exception {
-        // Set service.
-        disableService();
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Assert callback was called
-        final View username = mActivity.getUsername();
-        callback.assertUiUnavailableEvent(username);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackNoDatasets() throws Exception {
-        callbackUnavailableTest(NO_RESPONSE);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackNoDatasetsButSaveInfo() throws Exception {
-        callbackUnavailableTest(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-    }
-
-    private void callbackUnavailableTest(CannedFillResponse response) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(response);
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        mUiBot.assertNoDatasetsEver();
-
-        // Assert callback was called
-        final View username = mActivity.getUsername();
-        callback.assertUiUnavailableEvent(username);
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndSave() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Bundle extras = new Bundle();
-        extras.putString("numbers", "4815162342");
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setId("I'm the alpha and the omega")
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setExtras(extras)
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Since this is a Presubmit test, wait for connection to avoid flakiness.
-        waitUntilConnected();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Make sure input was sanitized...
-        assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
-        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
-
-        // ...but labels weren't
-        assertTextOnly(fillRequest.structure, ID_USERNAME_LABEL, "Username");
-        assertTextOnly(fillRequest.structure, ID_PASSWORD_LABEL, "Password");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        assertViewAutofillState(mActivity.getPassword(), true);
-
-        // Try to login, it will fail.
-        final String loginMessage = mActivity.tapLogin();
-
-        assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(AUTHENTICATION_MESSAGE);
-
-        // Set right password...
-        mActivity.onPassword((v) -> v.setText("dude"));
-        assertViewAutofillState(mActivity.getPassword(), false);
-
-        // ... and try again
-        final String expectedMessage = getWelcomeMessage("dude");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
-
-        // Assert value of expected fields - should not be sanitized.
-        assertTextAndValue(saveRequest.structure, ID_USERNAME, "dude");
-        assertTextAndValue(saveRequest.structure, ID_PASSWORD, "dude");
-        assertTextOnly(saveRequest.structure, ID_USERNAME_LABEL, "Username");
-        assertTextOnly(saveRequest.structure, ID_PASSWORD_LABEL, "Password");
-
-        // Make sure extras were passed back on onSave()
-        assertThat(saveRequest.data).isNotNull();
-        final String extraValue = saveRequest.data.getString("numbers");
-        assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndSaveHidingOverlays() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Bundle extras = new Bundle();
-        extras.putString("numbers", "4815162342");
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setExtras(extras)
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Since this is a Presubmit test, wait for connection to avoid flakiness.
-        waitUntilConnected();
-
-        sReplier.getNextFillRequest();
-
-        // Add an overlay on top of the whole screen
-        final View[] overlay = new View[1];
-        try {
-            // Allow ourselves to add overlays
-            allowOverlays();
-
-            // Make sure the fill UI is shown.
-            mUiBot.assertDatasets("The Dude");
-
-            final CountDownLatch latch = new CountDownLatch(1);
-
-            mActivity.runOnUiThread(() -> {
-                // This overlay is focusable, full-screen, which should block interaction
-                // with the fill UI unless the platform successfully hides overlays.
-                final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-                params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-                params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
-                params.width = ViewGroup.LayoutParams.MATCH_PARENT;
-                params.height = ViewGroup.LayoutParams.MATCH_PARENT;
-
-                final View view = new View(mContext) {
-                    @Override
-                    protected void onAttachedToWindow() {
-                        super.onAttachedToWindow();
-                        latch.countDown();
-                    }
-                };
-                view.setBackgroundColor(Color.RED);
-                WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-                windowManager.addView(view, params);
-                overlay[0] = view;
-            });
-
-            // Wait for the window being added.
-            assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
-
-            // Auto-fill it.
-            mUiBot.selectDataset("The Dude");
-
-            // Check the results.
-            mActivity.assertAutoFilled();
-
-            // Try to login, it will fail.
-            final String loginMessage = mActivity.tapLogin();
-
-            assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(
-                    AUTHENTICATION_MESSAGE);
-
-            // Set right password...
-            mActivity.onPassword((v) -> v.setText("dude"));
-
-            // ... and try again
-            final String expectedMessage = getWelcomeMessage("dude");
-            final String actualMessage = mActivity.tapLogin();
-            assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-            // Assert the snack bar is shown and tap "Save".
-            mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-            final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-            // Assert value of expected fields - should not be sanitized.
-            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-            assertTextAndValue(username, "dude");
-            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-            assertTextAndValue(password, "dude");
-
-            // Make sure extras were passed back on onSave()
-            assertThat(saveRequest.data).isNotNull();
-            final String extraValue = saveRequest.data.getString("numbers");
-            assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
-        } finally {
-            try {
-                // Make sure we can no longer add overlays
-                disallowOverlays();
-                // Make sure the overlay is removed
-                mActivity.runOnUiThread(() -> {
-                    WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-                    windowManager.removeView(overlay[0]);
-                });
-            } catch (Exception e) {
-                mSafeCleanerRule.add(e);
-            }
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillMultipleDatasetsPickFirst() throws Exception {
-        multipleDatasetsTest(1);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillMultipleDatasetsPickSecond() throws Exception {
-        multipleDatasetsTest(2);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillMultipleDatasetsPickThird() throws Exception {
-        multipleDatasetsTest(3);
-    }
-
-    private void multipleDatasetsTest(int number) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "mr_plow")
-                        .setField(ID_PASSWORD, "D'OH!")
-                        .setPresentation(createPresentation("Mr Plow"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "el barto")
-                        .setField(ID_PASSWORD, "aycaramba!")
-                        .setPresentation(createPresentation("El Barto"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "mr sparkle")
-                        .setField(ID_PASSWORD, "Aw3someP0wer")
-                        .setPresentation(createPresentation("Mr Sparkle"))
-                        .build())
-                .build());
-        final String name;
-
-        switch (number) {
-            case 1:
-                name = "Mr Plow";
-                mActivity.expectAutoFill("mr_plow", "D'OH!");
-                break;
-            case 2:
-                name = "El Barto";
-                mActivity.expectAutoFill("el barto", "aycaramba!");
-                break;
-            case 3:
-                name = "Mr Sparkle";
-                mActivity.expectAutoFill("mr sparkle", "Aw3someP0wer");
-                break;
-            default:
-                throw new IllegalArgumentException("invalid dataset number: " + number);
-        }
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure all datasets are shown.
-        final UiObject2 picker = mUiBot.assertDatasets("Mr Plow", "El Barto", "Mr Sparkle");
-
-        // Auto-fill it.
-        mUiBot.selectDataset(picker, name);
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    /**
-     * Tests the scenario where the service uses custom remote views for different fields (username
-     * and password).
-     */
-    @Test
-    public void testAutofillOneDatasetCustomPresentation() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude",
-                        createPresentation("The Dude"))
-                .setField(ID_PASSWORD, "sweet",
-                        createPresentation("Dude's password"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Check initial field.
-        mUiBot.assertDatasets("The Dude");
-
-        // Then move around...
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("Dude's password");
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("The Dude");
-
-        // Auto-fill it.
-        requestFocusOnPassword();
-        mUiBot.selectDataset("Dude's password");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    /**
-     * Tests the scenario where the service uses custom remote views for different fields (username
-     * and password) and the dataset itself, and each dataset has the same number of fields.
-     */
-    @Test
-    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
-    public void testAutofillMultipleDatasetsCustomPresentations() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder(createPresentation("Dataset1"))
-                        .setField(ID_USERNAME, "user1") // no presentation
-                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
-                        .setField(ID_PASSWORD, "pass2") // no presentation
-                        .setPresentation(createPresentation("Dataset2"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("user1", "pass1");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Check initial field.
-        mUiBot.assertDatasets("Dataset1", "User2");
-
-        // Then move around...
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("Pass1", "Dataset2");
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("Dataset1", "User2");
-
-        // Auto-fill it.
-        requestFocusOnPassword();
-        mUiBot.selectDataset("Pass1");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    /**
-     * Tests the scenario where the service uses custom remote views for different fields (username
-     * and password), and each dataset has the same number of fields.
-     */
-    @Test
-    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
-    public void testAutofillMultipleDatasetsCustomPresentationSameFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
-                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
-                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("user1", "pass1");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Check initial field.
-        mUiBot.assertDatasets("User1", "User2");
-
-        // Then move around...
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("Pass1", "Pass2");
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("User1", "User2");
-
-        // Auto-fill it.
-        requestFocusOnPassword();
-        mUiBot.selectDataset("Pass1");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    /**
-     * Tests the scenario where the service uses custom remote views for different fields (username
-     * and password), but each dataset has a different number of fields.
-     */
-    @Test
-    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
-    public void testAutofillMultipleDatasetsCustomPresentationFirstDatasetMissingSecondField()
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
-                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("user2", "pass2");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Check initial field.
-        mUiBot.assertDatasets("User1", "User2");
-
-        // Then move around...
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("Pass2");
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("User1", "User2");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("User2");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    /**
-     * Tests the scenario where the service uses custom remote views for different fields (username
-     * and password), but each dataset has a different number of fields.
-     */
-    @Test
-    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
-    public void testAutofillMultipleDatasetsCustomPresentationSecondDatasetMissingFirstField()
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
-                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("user1", "pass1");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Check initial field.
-        mUiBot.assertDatasets("User1");
-
-        // Then move around...
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("Pass1", "Pass2");
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("User1");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("User1");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnly() throws Exception {
-        saveOnlyTest(false);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnlyTriggeredManually() throws Exception {
-        saveOnlyTest(false);
-    }
-
-    private void saveOnlyTest(boolean manually) throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger auto-fill.
-        if (manually) {
-            mActivity.forceAutofillOnUsername();
-        } else {
-            mActivity.onUsername(View::requestFocus);
-        }
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNoUnhandledSaveRequests();
-        assertThat(saveRequest.datasetIds).isNull();
-
-        // Assert value of expected fields - should not be sanitized.
-        try {
-            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-            assertTextAndValue(username, "malkovich");
-            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-            assertTextAndValue(password, "malkovich");
-        } catch (AssertionError | RuntimeException e) {
-            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
-            throw e;
-        }
-    }
-
-    @Test
-    public void testSaveGoesAwayWhenTappingHomeButton() throws Exception {
-        saveGoesAway(DismissType.HOME_BUTTON);
-    }
-
-    @Test
-    public void testSaveGoesAwayWhenTappingBackButton() throws Exception {
-        saveGoesAway(DismissType.BACK_BUTTON);
-    }
-
-    @Test
-    public void testSaveGoesAwayWhenTouchingOutside() throws Exception {
-        saveGoesAway(DismissType.TOUCH_OUTSIDE);
-    }
-
-    private void saveGoesAway(DismissType dismissType) throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Then make sure it goes away when user doesn't want it..
-        switch (dismissType) {
-            case BACK_BUTTON:
-                mUiBot.pressBack();
-                break;
-            case HOME_BUTTON:
-                mUiBot.pressHome();
-                break;
-            case TOUCH_OUTSIDE:
-                mUiBot.assertShownByText(expectedMessage).click();
-                break;
-            default:
-                throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
-        }
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnlyPreFilled() throws Exception {
-        saveOnlyTestPreFilled(false);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnlyTriggeredManuallyPreFilled() throws Exception {
-        saveOnlyTestPreFilled(true);
-    }
-
-    private void saveOnlyTestPreFilled(boolean manually) throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Set activity
-        mActivity.onUsername((v) -> v.setText("user_before"));
-        mActivity.onPassword((v) -> v.setText("pass_before"));
-
-        // Trigger auto-fill.
-        if (manually) {
-            // setText() will trigger a fill request.
-            // Waits the first fill request triggered by the setText() is received by the service to
-            // avoid flaky.
-            sReplier.getNextFillRequest();
-            mUiBot.waitForIdle();
-
-            // Set expectations again.
-            sReplier.addResponse(new CannedFillResponse.Builder()
-                    .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                    .build());
-            mActivity.forceAutofillOnUsername();
-        } else {
-            mUiBot.selectByRelativeId(ID_USERNAME);
-        }
-        mUiBot.waitForIdle();
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("user_after"));
-        mActivity.onPassword((v) -> v.setText("pass_after"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("user_after");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.waitForIdle();
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNoUnhandledSaveRequests();
-
-        // Assert value of expected fields - should not be sanitized.
-        try {
-            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-            assertTextAndValue(username, "user_after");
-            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-            assertTextAndValue(password, "pass_after");
-        } catch (AssertionError | RuntimeException e) {
-            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
-            throw e;
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnlyTwoRequiredFieldsOnePrefilled() throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Set activity
-        mActivity.onUsername((v) -> v.setText("I_AM_USER"));
-
-        // Trigger auto-fill.
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before changing value, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Set credentials...
-        mActivity.onPassword((v) -> v.setText("thou should pass")); // contains pass
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("I_AM_USER"); // contains pass
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNoUnhandledSaveRequests();
-
-        // Assert value of expected fields - should not be sanitized.
-        try {
-            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-            assertTextAndValue(username, "I_AM_USER");
-            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-            assertTextAndValue(password, "thou should pass");
-        } catch (AssertionError | RuntimeException e) {
-            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
-            throw e;
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnlyOptionalField() throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME)
-                .setOptionalSavableIds(ID_PASSWORD)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword(View::requestFocus);
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Assert value of expected fields - should not be sanitized.
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(username, "malkovich");
-        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-        assertTextAndValue(password, "malkovich");
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveNoRequiredField_NoneFilled() throws Exception {
-        optionalOnlyTest(FilledFields.NONE);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveNoRequiredField_OneFilled() throws Exception {
-        optionalOnlyTest(FilledFields.USERNAME_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveNoRequiredField_BothFilled() throws Exception {
-        optionalOnlyTest(FilledFields.BOTH);
-    }
-
-    enum FilledFields {
-        NONE,
-        USERNAME_ONLY,
-        BOTH
-    }
-
-    private void optionalOnlyTest(FilledFields filledFields) throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD)
-                .setOptionalSavableIds(ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        final String expectedUsername;
-        if (filledFields == FilledFields.USERNAME_ONLY || filledFields == FilledFields.BOTH) {
-            expectedUsername = BACKDOOR_USERNAME;
-            mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
-        } else {
-            expectedUsername = "";
-        }
-        mActivity.onPassword(View::requestFocus);
-        if (filledFields == FilledFields.BOTH) {
-            mActivity.onPassword((v) -> v.setText("whatever"));
-        }
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage(expectedUsername);
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        if (filledFields == FilledFields.NONE) {
-            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-            return;
-        }
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Assert value of expected fields - should not be sanitized.
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(username, BACKDOOR_USERNAME);
-
-        if (filledFields == FilledFields.BOTH) {
-            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-            assertTextAndValue(password, "whatever");
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testGenericSave() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSavePassword() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveAddress() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_ADDRESS);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveCreditCard() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveUsername() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_USERNAME);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveEmailAddress() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_EMAIL_ADDRESS);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveDebitCard() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_DEBIT_CARD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSavePaymentCard() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_PAYMENT_CARD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveGenericCard() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_GENERIC_CARD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveTwoCardTypes() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD,
-                SAVE_DATA_TYPE_GENERIC_CARD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveThreeCardTypes() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD
-                | SAVE_DATA_TYPE_PAYMENT_CARD, SAVE_DATA_TYPE_GENERIC_CARD);
-    }
-
-    private void customizedSaveTest(int type) throws Exception {
-        customizedSaveTest(type, type);
-    }
-
-    private void customizedSaveTest(int type, int expectedType) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final String saveDescription = "Your data will be saved with love and care...";
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(type, ID_USERNAME, ID_PASSWORD)
-                .setSaveDescription(saveDescription)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started.
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        final UiObject2 saveSnackBar = mUiBot.assertSaveShowing(saveDescription, expectedType);
-        mUiBot.saveForAutofill(saveSnackBar, true);
-
-        // Assert save was called.
-        sReplier.getNextSaveRequest();
-    }
-
-    @Test
-    public void testDontTriggerSaveOnFinishWhenRequestedByFlag() throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Make sure it didn't trigger save.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndSaveWhenFlagSecure() throws Exception {
-        mActivity.setFlags(FLAG_SECURE);
-        testAutoFillOneDatasetAndSave();
-    }
-
-    @Test
-    public void testAutoFillOneDatasetWhenFlagSecure() throws Exception {
-        mActivity.setFlags(FLAG_SECURE);
-        testAutoFillOneDataset();
-    }
-
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testDisableSelf() throws Exception {
-        enableService();
-
-        // Can disable while connected.
-        mActivity.runOnUiThread(() -> mContext.getSystemService(
-                AutofillManager.class).disableAutofillServices());
-
-        // Ensure disabled.
-        assertServiceDisabled();
-    }
-
-    @Test
-    public void testNeverRejectStyleNegativeSaveButton() throws Exception {
-        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_NEVER);
-    }
-
-    @Test
-    public void testRejectStyleNegativeSaveButton() throws Exception {
-        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT);
-    }
-
-    @Test
-    public void testCancelStyleNegativeSaveButton() throws Exception {
-        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL);
-    }
-
-    private void negativeSaveButtonStyle(int style) throws Exception {
-        enableService();
-
-        // Set service behavior.
-
-        final String intentAction = "android.autofillservice.cts.CUSTOM_ACTION";
-
-        // Configure the save UI.
-        final IntentSender listener = PendingIntent.getBroadcast(
-                mContext, 0, new Intent(intentAction), 0).getIntentSender();
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setNegativeAction(style, listener)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText("foo"));
-        mActivity.onPassword((v) -> v.setText("foo"));
-        mActivity.tapLogin();
-
-        // Start watching for the negative intent
-        final CountDownLatch latch = new CountDownLatch(1);
-        final IntentFilter intentFilter = new IntentFilter(intentAction);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                mContext.unregisterReceiver(this);
-                latch.countDown();
-            }
-        }, intentFilter);
-
-        // Trigger the negative button.
-        mUiBot.saveForAutofill(style, /* yesDoIt= */ false, SAVE_DATA_TYPE_PASSWORD);
-
-        // Wait for the custom action.
-        assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
-    }
-
-    @Test
-    public void testContinueStylePositiveSaveButton() throws Exception {
-        enableService();
-
-        // Set service behavior.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setPositiveAction(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText("foo"));
-        mActivity.onPassword((v) -> v.setText("foo"));
-        mActivity.tapLogin();
-
-        // Start watching for the negative intent
-        // Trigger the negative button.
-        mUiBot.saveForAutofill(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert save was called.
-        sReplier.getNextSaveRequest();
-    }
-
-    @Test
-    @AppModeFull(reason = "Unit test")
-    public void testGetTextInputType() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Assert input text on fill request:
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        final ViewNode label = findNodeByResourceId(fillRequest.structure, ID_PASSWORD_LABEL);
-        assertThat(label.getInputType()).isEqualTo(TYPE_NULL);
-        final ViewNode password = findNodeByResourceId(fillRequest.structure, ID_PASSWORD);
-        assertWithMessage("No TYPE_TEXT_VARIATION_PASSWORD on %s", password.getInputType())
-                .that(password.getInputType() & TYPE_TEXT_VARIATION_PASSWORD)
-                .isEqualTo(TYPE_TEXT_VARIATION_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "Unit test")
-    public void testNoContainers() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        mUiBot.assertNoDatasetsEver();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert it only has 1 root view with 10 "leaf" nodes:
-        // 1.text view for app title
-        // 2.username text label
-        // 3.username text field
-        // 4.password text label
-        // 5.password text field
-        // 6.output text field
-        // 7.clear button
-        // 8.save button
-        // 9.login button
-        // 10.cancel button
-        //
-        // But it also has an intermediate container (for username) that should be included because
-        // it has a resource id.
-
-        assertNumberOfChildren(fillRequest.structure, 12);
-
-        // Make sure container with a resource id was included:
-        final ViewNode usernameContainer = findNodeByResourceId(fillRequest.structure,
-                ID_USERNAME_CONTAINER);
-        assertThat(usernameContainer).isNotNull();
-        assertThat(usernameContainer.getChildCount()).isEqualTo(2);
-    }
-
-    @Test
-    public void testAutofillManuallyOneDataset() throws Exception {
-        // Set service.
-        enableService();
-
-        // And activity.
-        mActivity.onUsername((v) -> v.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO));
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Explicitly uses the contextual menu to test that functionality.
-        mUiBot.getAutofillMenuOption(ID_USERNAME).click();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
-
-        // Should have been automatically filled.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillManuallyOneDatasetWhenClipboardFull() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set clipboard.
-        ClipboardManager cm = (ClipboardManager) mActivity.getSystemService(CLIPBOARD_SERVICE);
-        cm.setPrimaryClip(ClipData.newPlainText(null, "test"));
-
-        // And activity.
-        mActivity.onUsername((v) -> v.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO));
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Explicitly uses the contextual menu to test that functionality.
-        mUiBot.getAutofillMenuOption(ID_USERNAME).click();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
-
-        // Should have been automatically filled.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // clear clipboard
-        cm.clearPrimaryClip();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception {
-        autofillManuallyTwoDatasets(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception {
-        autofillManuallyTwoDatasets(false);
-    }
-
-    private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "jenny")
-                        .setField(ID_PASSWORD, "8675309")
-                        .setPresentation(createPresentation("Jenny"))
-                        .build())
-                .build());
-        if (pickFirst) {
-            mActivity.expectAutoFill("dude", "sweet");
-        } else {
-            mActivity.expectAutoFill("jenny", "8675309");
-
-        }
-
-        // Force a manual autofill request.
-        mActivity.forceAutofillOnUsername();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
-
-        // Auto-fill it.
-        final UiObject2 picker = mUiBot.assertDatasets("The Dude", "Jenny");
-        mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyPartialField() throws Exception {
-        // Set service.
-        enableService();
-
-        sReplier.addResponse(NO_RESPONSE);
-        // And activity.
-        mActivity.onUsername((v) -> v.setText("dud"));
-        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
-
-        // setText() will trigger a fill request.
-        // Waits the first fill request triggered by the setText() is received by the service to
-        // avoid flaky.
-        sReplier.getNextFillRequest();
-        mUiBot.waitForIdle();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Force a manual autofill request.
-        mActivity.forceAutofillOnUsername();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
-        // Username value should be available because it triggered the manual request...
-        assertValue(fillRequest.structure, ID_USERNAME, "dud");
-        // ... but password didn't
-        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
-
-        // Selects the dataset.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyAgainAfterAutomaticallyAutofilledBefore() throws Exception {
-        // Set service.
-        enableService();
-
-        /*
-         * 1st fill (automatic).
-         */
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Assert request.
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-        assertTextIsSanitized(fillRequest1.structure, ID_USERNAME);
-        assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
-
-        // Select it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        /*
-         * 2nd fill (manual).
-         */
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "DUDE")
-                .setField(ID_PASSWORD, "SWEET")
-                .setPresentation(createPresentation("THE DUDE"))
-                .build());
-        mActivity.expectAutoFill("DUDE", "SWEET");
-        // Change password to make sure it's not sent to the service.
-        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
-
-        // Trigger auto-fill.
-        mActivity.forceAutofillOnUsername();
-
-        // Assert request.
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
-        assertValue(fillRequest2.structure, ID_USERNAME, "dude");
-        assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
-
-        // Select it.
-        mUiBot.selectDataset("THE DUDE");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyAgainAfterManuallyAutofilledBefore() throws Exception {
-        // Set service.
-        enableService();
-
-        /*
-         * 1st fill (manual).
-         */
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.forceAutofillOnUsername();
-
-        // Assert request.
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest1.flags, FLAG_MANUAL_REQUEST);
-        assertValue(fillRequest1.structure, ID_USERNAME, "");
-        assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
-
-        // Select it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        /*
-         * 2nd fill (manual).
-         */
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "DUDE")
-                .setField(ID_PASSWORD, "SWEET")
-                .setPresentation(createPresentation("THE DUDE"))
-                .build());
-        mActivity.expectAutoFill("DUDE", "SWEET");
-        // Change password to make sure it's not sent to the service.
-        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
-
-        // Trigger auto-fill.
-        mActivity.forceAutofillOnUsername();
-
-        // Assert request.
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
-        assertValue(fillRequest2.structure, ID_USERNAME, "dude");
-        assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
-
-        // Select it.
-        mUiBot.selectDataset("THE DUDE");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testCommitMultipleTimes() throws Throwable {
-        // Set service.
-        enableService();
-
-        final CannedFillResponse response = new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build();
-
-        for (int i = 1; i <= 10; i++) {
-            Log.i(TAG, "testCommitMultipleTimes(): step " + i);
-            final String username = "user-" + i;
-            final String password = "pass-" + i;
-            try {
-                // Set expectations.
-                sReplier.addResponse(response);
-
-                Timeouts.IDLE_UNBIND_TIMEOUT.run("wait for session created", () -> {
-                    // Trigger auto-fill.
-                    mActivity.onUsername(View::clearFocus);
-                    mActivity.onUsername(View::requestFocus);
-
-                    return isConnected() ? "not_used" : null;
-                });
-
-                sReplier.getNextFillRequest();
-
-                // Validation check.
-                mUiBot.assertNoDatasetsEver();
-
-                // Set credentials...
-                mActivity.onUsername((v) -> v.setText(username));
-                mActivity.onPassword((v) -> v.setText(password));
-
-                // Change focus to prepare for next step - must do it before session is gone
-                mActivity.onPassword(View::requestFocus);
-
-                // ...and save them
-                mActivity.tapSave();
-
-                // Assert the snack bar is shown and tap "Save".
-                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-                // Assert value of expected fields - should not be sanitized.
-                final ViewNode usernameNode = findNodeByResourceId(saveRequest.structure,
-                        ID_USERNAME);
-                assertTextAndValue(usernameNode, username);
-                final ViewNode passwordNode = findNodeByResourceId(saveRequest.structure,
-                        ID_PASSWORD);
-                assertTextAndValue(passwordNode, password);
-
-                waitUntilDisconnected();
-
-                // Wait and check if the save window is correctly hidden.
-                mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-            } catch (RetryableException e) {
-                throw new RetryableException(e, "on step %d", i);
-            } catch (Throwable t) {
-                throw new Throwable("Error on step " + i, t);
-            }
-        }
-    }
-
-    @Test
-    public void testCancelMultipleTimes() throws Throwable {
-        // Set service.
-        enableService();
-
-        for (int i = 1; i <= 10; i++) {
-            Log.i(TAG, "testCancelMultipleTimes(): step " + i);
-            final String username = "user-" + i;
-            final String password = "pass-" + i;
-            sReplier.addResponse(new CannedDataset.Builder()
-                    .setField(ID_USERNAME, username)
-                    .setField(ID_PASSWORD, password)
-                    .setPresentation(createPresentation("The Dude"))
-                    .build());
-            mActivity.expectAutoFill(username, password);
-            try {
-                // Trigger auto-fill.
-                requestFocusOnUsername();
-
-                waitUntilConnected();
-                sReplier.getNextFillRequest();
-
-                // Auto-fill it.
-                mUiBot.selectDataset("The Dude");
-
-                // Check the results.
-                mActivity.assertAutoFilled();
-
-                // Change focus to prepare for next step - must do it before session is gone
-                requestFocusOnPassword();
-
-                // Rinse and repeat...
-                mActivity.tapClear();
-
-                waitUntilDisconnected();
-            } catch (RetryableException e) {
-                throw e;
-            } catch (Throwable t) {
-                throw new Throwable("Error on step " + i, t);
-            }
-        }
-    }
-
-    @Test
-    public void testClickCustomButton() throws Exception {
-        // Set service.
-        enableService();
-
-        Intent intent = new Intent(mContext, EmptyActivity.class);
-        IntentSender sender = PendingIntent.getActivity(mContext, 0, intent,
-                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT)
-                .getIntentSender();
-
-        RemoteViews presentation = new RemoteViews(mPackageName, R.layout.list_item);
-        presentation.setTextViewText(R.id.text1, "Poke");
-        Intent firstIntent = new Intent(mContext, DummyActivity.class);
-        presentation.setOnClickPendingIntent(R.id.text1, PendingIntent.getActivity(
-                mContext, 0, firstIntent, PendingIntent.FLAG_ONE_SHOT
-                        | PendingIntent.FLAG_CANCEL_CURRENT));
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(sender, ID_USERNAME)
-                .setPresentation(presentation)
-                .build());
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Click on the custom button
-        mUiBot.selectByText("Poke");
-
-        // Make sure the click worked
-        mUiBot.selectByText("foo");
-
-        // Go back to the filled app.
-        mUiBot.pressBack();
-    }
-
-    @Test
-    public void testIsServiceEnabled() throws Exception {
-        disableService();
-        final AutofillManager afm = mActivity.getAutofillManager();
-        assertThat(afm.hasEnabledAutofillServices()).isFalse();
-        try {
-            enableService();
-            assertThat(afm.hasEnabledAutofillServices()).isTrue();
-        } finally {
-            disableService();
-        }
-    }
-
-    @Test
-    public void testGetAutofillServiceComponentName() throws Exception {
-        final AutofillManager afm = mActivity.getAutofillManager();
-
-        enableService();
-        final ComponentName componentName = afm.getAutofillServiceComponentName();
-        assertThat(componentName.getPackageName()).isEqualTo(SERVICE_PACKAGE);
-        assertThat(componentName.getClassName()).endsWith(SERVICE_CLASS);
-
-        disableService();
-        assertThat(afm.getAutofillServiceComponentName()).isNull();
-    }
-
-    @Test
-    public void testSetupComplete() throws Exception {
-        enableService();
-
-        // Validation check.
-        final AutofillManager afm = mActivity.getAutofillManager();
-        Helper.assertAutofillEnabled(afm, true);
-
-        // Now disable user_complete and try again.
-        try {
-            setUserComplete(mContext, false);
-            Helper.assertAutofillEnabled(afm, false);
-        } finally {
-            setUserComplete(mContext, true);
-        }
-    }
-
-    @Test
-    public void testPopupGoesAwayWhenServiceIsChanged() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-
-        // Now disable service by setting another service
-        Helper.enableAutofillService(mContext, NoOpAutofillService.SERVICE_NAME);
-
-        // ...and make sure popup's gone
-        mUiBot.assertNoDatasets();
-    }
-
-    // TODO(b/70682223): add a new test to make sure service with BIND_AUTOFILL permission works
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testServiceIsDisabledWhenNewServiceInfoIsInvalid() throws Exception {
-        serviceIsDisabledWhenNewServiceIsInvalid(BadAutofillService.SERVICE_NAME);
-    }
-
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testServiceIsDisabledWhenNewServiceNameIsInvalid() throws Exception {
-        serviceIsDisabledWhenNewServiceIsInvalid("Y_U_NO_VALID");
-    }
-
-    private void serviceIsDisabledWhenNewServiceIsInvalid(String serviceName) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-
-        // Now disable service by setting another service...
-        Helper.enableAutofillService(mContext, serviceName);
-
-        // ...and make sure popup's gone
-        mUiBot.assertNoDatasets();
-
-        // Then try to trigger autofill again...
-        mActivity.onPassword(View::requestFocus);
-        //...it should not work!
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    @Test
-    public void testAutofillMovesCursorToTheEnd() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // NOTE: need to call getSelectionEnd() inside the UI thread, otherwise it returns 0
-        final AtomicInteger atomicBombToKillASmallInsect = new AtomicInteger();
-
-        mActivity.onUsername((v) -> atomicBombToKillASmallInsect.set(v.getSelectionEnd()));
-        assertWithMessage("Wrong position on username").that(atomicBombToKillASmallInsect.get())
-                .isEqualTo(4);
-
-        mActivity.onPassword((v) -> atomicBombToKillASmallInsect.set(v.getSelectionEnd()));
-        assertWithMessage("Wrong position on password").that(atomicBombToKillASmallInsect.get())
-                .isEqualTo(5);
-    }
-
-    @Test
-    public void testAutofillLargeNumberOfDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        final StringBuilder bigStringBuilder = new StringBuilder();
-        for (int i = 0; i < 10_000 ; i++) {
-            bigStringBuilder.append("BigAmI");
-        }
-        final String bigString = bigStringBuilder.toString();
-
-        final int size = 100;
-        Log.d(TAG, "testAutofillLargeNumberOfDatasets(): " + size + " datasets with "
-                + bigString.length() +"-bytes id");
-
-        final CannedFillResponse.Builder response = new CannedFillResponse.Builder();
-        for (int i = 0; i < size; i++) {
-            final String suffix = "-" + (i + 1);
-            response.addDataset(new CannedDataset.Builder()
-                    .setField(ID_USERNAME, "user" + suffix)
-                    .setField(ID_PASSWORD, "pass" + suffix)
-                    .setId(bigString)
-                    .setPresentation(createPresentation("DS" + suffix))
-                    .build());
-        }
-
-        // Set expectations.
-        sReplier.addResponse(response.build());
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure all datasets are shown.
-        // TODO: improve assertDatasets() so it supports scrolling, and assert all of them are
-        // shown. In fullscreen there are 4 items, otherwise there are 3 items.
-        mUiBot.assertDatasetsContains("DS-1", "DS-2", "DS-3");
-
-        // TODO: once it supports scrolling, selects the last dataset and asserts it's filled.
-    }
-
-    @Test
-    public void testCancellationSignalCalledAfterTimeout() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final OneTimeCancellationSignalListener listener =
-                new OneTimeCancellationSignalListener(Timeouts.FILL_TIMEOUT.ms() + 2000);
-        sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Attach listener to CancellationSignal.
-        waitUntilConnected();
-        sReplier.getNextFillRequest().cancellationSignal.setOnCancelListener(listener);
-
-        // Assert results
-        listener.assertOnCancelCalled();
-    }
-
-    @Test
-    @AppModeFull(reason = "Unit test")
-    public void testNewTextAttributes() throws Exception {
-        enableService();
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.onUsername(View::requestFocus);
-
-        final FillRequest request = sReplier.getNextFillRequest();
-        final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
-        assertThat(username.getMinTextEms()).isEqualTo(2);
-        assertThat(username.getMaxTextEms()).isEqualTo(5);
-        assertThat(username.getMaxTextLength()).isEqualTo(25);
-
-        final ViewNode container = findNodeByResourceId(request.structure, ID_USERNAME_CONTAINER);
-        assertThat(container.getMinTextEms()).isEqualTo(-1);
-        assertThat(container.getMaxTextEms()).isEqualTo(-1);
-        assertThat(container.getMaxTextLength()).isEqualTo(-1);
-
-        final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
-        assertThat(password.getMinTextEms()).isEqualTo(-1);
-        assertThat(password.getMaxTextEms()).isEqualTo(-1);
-        // Security fix a0c6539 limits the text length 5000. Disable assert text length to avoid
-        // break the public release.
-        //assertThat(password.getMaxTextLength()).isEqualTo(-1);
-    }
-
-    @Test
-    public void testUiShowOnChangeAfterAutofill() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude", createPresentation("dude"))
-                .setField(ID_PASSWORD, "sweet", createPresentation("sweet"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("dude");
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        mUiBot.assertNoDatasets();
-
-        // Delete a character.
-        sendKeyEvent("KEYCODE_DEL");
-        assertThat(mUiBot.getTextByRelativeId(ID_USERNAME)).isEqualTo("dud");
-
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Check autofill UI show.
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("dude");
-
-        // Autofill again.
-        mUiBot.selectDataset(datasetPicker, "dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    public void testUiShowOnChangeAfterAutofillOnePresentation() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("The Dude");
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        mUiBot.assertNoDatasets();
-
-        // Delete username
-        mUiBot.setTextByRelativeId(ID_USERNAME, "");
-
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Check autofill UI show.
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
-
-        // Autofill again.
-        mUiBot.selectDataset(datasetPicker, "The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    public void testCancelActionButton() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentationWithCancel("The Dude"))
-                        .build())
-                .setPresentationCancelIds(new int[]{R.id.cancel_fill});
-        sReplier.addResponse(builder.build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasetsContains("The Dude");
-
-        // Tap cancel button on fill UI
-        mUiBot.selectByRelativeId(ID_CANCEL_FILL);
-        mUiBot.waitForIdle();
-
-        mUiBot.assertNoDatasets();
-
-        // Test and verify auto-fill does not trigger
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.waitForIdle();
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Test and verify auto-fill does not trigger.
-        mActivity.onUsername(View::requestFocus);
-        mUiBot.waitForIdle();
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Reset
-        mActivity.tapClear();
-
-        // Set expectations.
-        final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentationWithCancel("The Dude"))
-                        .build())
-                .setPresentationCancelIds(new int[]{R.id.cancel});
-        sReplier.addResponse(builder2.build());
-
-        // Trigger auto-fill.
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Verify auto-fill has been triggered.
-        mUiBot.assertDatasetsContains("The Dude");
-    }
-
-    @Test
-    @AppModeFull(reason = "WRITE_SECURE_SETTING permission can't be grant to instant apps")
-    public void testSwitchInputMethod_noNewFillRequest() throws Exception {
-        // Set service
-        enableService();
-
-        // Set expectations
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build());
-        sReplier.addResponse(builder.build());
-
-        // Trigger auto-fill
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasetsContains("The Dude");
-
-        // Trigger IME switch event
-        Helper.mockSwitchInputMethod(sContext);
-        mUiBot.waitForIdleSync();
-
-        // Tap password field
-        mUiBot.selectByRelativeId(ID_PASSWORD);
-        mUiBot.waitForIdleSync();
-
-        mUiBot.assertDatasetsContains("The Dude");
-
-        // No new fill request
-        sReplier.assertNoUnhandledFillRequests();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillActivity.java
deleted file mode 100644
index 40ebb69..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-/**
- * Same as {@link LoginActivity}, but with autofill disabled.
- */
-public class LoginNotImportantForAutofillActivity extends LoginActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.login_activity_not_important;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedActivityContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedActivityContextActivity.java
deleted file mode 100644
index 035cea6..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedActivityContextActivity.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.util.Log;
-
-/**
- * Same as {@link LoginNotImportantForAutofillActivity}, but using a context wrapper of itself
- * as the base context.
- */
-public class LoginNotImportantForAutofillWrappedActivityContextActivity
-        extends LoginNotImportantForAutofillActivity {
-
-    private Context mMyBaseContext;
-
-    @Override
-    public Context getBaseContext() {
-        if (mMyBaseContext == null) {
-            mMyBaseContext = new ContextWrapper(super.getBaseContext());
-            Log.d(mTag, "getBaseContext(): set to " + mMyBaseContext + " (instead of "
-                    + super.getBaseContext() + ")");
-        }
-        return mMyBaseContext;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedApplicationContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedApplicationContextActivity.java
deleted file mode 100644
index b47cfc6..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedApplicationContextActivity.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.util.Log;
-
-/**
- * Same as {@link LoginNotImportantForAutofillActivity}, but using a context wrapper of itself
- * as the base context.
- */
-public class LoginNotImportantForAutofillWrappedApplicationContextActivity
-        extends LoginNotImportantForAutofillActivity {
-
-    private Context mMyBaseContext;
-
-    @Override
-    public Context getBaseContext() {
-        if (mMyBaseContext == null) {
-            mMyBaseContext = new ContextWrapper(getApplicationContext());
-            Log.d(mTag, "getBaseContext(): set to " + mMyBaseContext + " (instead of "
-                    + super.getBaseContext() + ")");
-        }
-        return mMyBaseContext;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivity.java
deleted file mode 100644
index c379f71..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivity.java
+++ /dev/null
@@ -1,27 +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.autofillservice.cts;
-
-/**
- * Same as {@link LoginActivity}, but with a custom autofill highlight drawable.
- */
-public class LoginWithCustomHighlightActivity extends LoginActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.login_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java
index 0812ad7..f1a06ae 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java
@@ -16,10 +16,15 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
 
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.activities.LoginWithCustomHighlightActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.MyDrawable;
 import android.graphics.Rect;
 import android.support.test.uiautomator.UiObject2;
 import android.view.View;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java
deleted file mode 100644
index 90c3e93..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java
+++ /dev/null
@@ -1,27 +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.autofillservice.cts;
-
-/**
- * Same as {@link LoginActivity}, but with the texts for some fields set from resources.
- */
-public class LoginWithStringsActivity extends LoginActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.login_with_strings_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
deleted file mode 100644
index d702052..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
+++ /dev/null
@@ -1,156 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertHintFromResources;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextFromResources;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
-import static android.autofillservice.cts.LoginActivity.AUTHENTICATION_MESSAGE;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.view.View;
-
-import org.junit.Test;
-
-@AppModeFull(reason = "LoginActivityTest is enough")
-public class LoginWithStringsActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<LoginWithStringsActivity> {
-
-    private LoginWithStringsActivity mActivity;
-
-
-    @Override
-    protected AutofillActivityTestRule<LoginWithStringsActivity> getActivityRule() {
-        return new AutofillActivityTestRule<LoginWithStringsActivity>(
-                LoginWithStringsActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndSave() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Bundle extras = new Bundle();
-        extras.putString("numbers", "4815162342");
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setId("I'm the alpha and the omega")
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setExtras(extras)
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-        waitUntilConnected();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Make sure input was sanitized.
-        assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
-        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
-
-        // Make sure labels were not sanitized
-        assertTextFromResources(fillRequest.structure, ID_USERNAME_LABEL, "Username", false,
-                "username_string");
-        assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "Password", false,
-                "password_string");
-
-        // Check text hints
-        assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
-                "username_hint");
-        assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
-                "password_hint");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Try to login, it will fail.
-        final String loginMessage = mActivity.tapLogin();
-
-        assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(AUTHENTICATION_MESSAGE);
-
-        // Set right password...
-        mActivity.onPassword((v) -> v.setText("dude"));
-
-        // ... and try again
-        final String expectedMessage = getWelcomeMessage("dude");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
-
-        // Assert value of expected fields - should not be sanitized.
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(username, "dude");
-        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(password, "dude");
-
-        // Make sure labels were not sanitized
-        assertTextFromResources(saveRequest.structure, ID_USERNAME_LABEL, "Username", false,
-                "username_string");
-        assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "Password", false,
-                "password_string");
-
-        // Check text hints
-        assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
-                "username_hint");
-        assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
-                "password_hint");
-
-        // Make sure extras were passed back on onSave()
-        assertThat(saveRequest.data).isNotNull();
-        final String extraValue = saveRequest.data.getString("numbers");
-        assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LuhnChecksumValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/LuhnChecksumValidatorTest.java
deleted file mode 100644
index 6eb8b8e..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LuhnChecksumValidatorTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.LuhnChecksumValidator;
-import android.service.autofill.ValueFinder;
-import android.view.autofill.AutofillId;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class LuhnChecksumValidatorTest {
-
-    @Test
-    public void nullId() {
-        assertThrows(NullPointerException.class,
-                () -> new LuhnChecksumValidator((AutofillId[]) null));
-    }
-
-    @Test
-    public void nullAndOtherId() {
-        assertThrows(NullPointerException.class,
-                () -> new LuhnChecksumValidator(new AutofillId(1), null));
-    }
-
-    @Test
-    public void duplicateFields() {
-        AutofillId id = new AutofillId(1);
-
-        // duplicate fields are allowed
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id, id);
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        // 5 is a valid checksum for 0005000
-        when(finder.findByAutofillId(id)).thenReturn("0005");
-        assertThat(validator.isValid(finder)).isTrue();
-
-        // 6 is a not a valid checksum for 0006000
-        when(finder.findByAutofillId(id)).thenReturn("0006");
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-
-    @Test
-    public void leadingZerosAreIgnored() {
-        AutofillId id = new AutofillId(1);
-
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("7992739871-3");
-        assertThat(validator.isValid(finder)).isTrue();
-
-        when(finder.findByAutofillId(id)).thenReturn("07992739871-3");
-        assertThat(validator.isValid(finder)).isTrue();
-    }
-
-    @Test
-    public void onlyOneChecksumValid() {
-        AutofillId id = new AutofillId(1);
-
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        for (int i = 0; i < 10; i++) {
-            when(finder.findByAutofillId(id)).thenReturn("7992739871-" + i);
-            assertThat(validator.isValid(finder)).isEqualTo(i == 3);
-        }
-    }
-
-    @Test
-    public void nullAutofillValuesCauseFailure() {
-        AutofillId id1 = new AutofillId(1);
-        AutofillId id2 = new AutofillId(2);
-        AutofillId id3 = new AutofillId(3);
-
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id1, id2, id3);
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("7992739871");
-        when(finder.findByAutofillId(id2)).thenReturn(null);
-        when(finder.findByAutofillId(id3)).thenReturn("3");
-
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-
-    @Test
-    public void nonDigits() {
-        AutofillId id = new AutofillId(1);
-
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
-
-        ValueFinder finder = mock(ValueFinder.class);
-        when(finder.findByAutofillId(id)).thenReturn("a7B9^9\n2 7{3\b9\08\uD83C\uDF2D7-1_3$");
-        assertThat(validator.isValid(finder)).isTrue();
-    }
-
-    @Test
-    public void multipleFieldNumber() {
-        AutofillId id1 = new AutofillId(1);
-        AutofillId id2 = new AutofillId(2);
-
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id1, id2);
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("7992739871");
-        when(finder.findByAutofillId(id2)).thenReturn("3");
-        assertThat(validator.isValid(finder)).isTrue();
-
-        when(finder.findByAutofillId(id2)).thenReturn("2");
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ManualAuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/ManualAuthenticationActivity.java
deleted file mode 100644
index b1983d1..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ManualAuthenticationActivity.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Activity;
-import android.app.assist.AssistStructure;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.view.autofill.AutofillManager;
-
-/**
- * An activity that authenticates on button press
- */
-public class ManualAuthenticationActivity extends Activity {
-    private static CannedFillResponse sResponse;
-    private static CannedFillResponse.CannedDataset sDataset;
-
-    public static void setResponse(CannedFillResponse response) {
-        sResponse = response;
-        sDataset = null;
-    }
-
-    public static void setDataset(CannedFillResponse.CannedDataset dataset) {
-        sDataset = dataset;
-        sResponse = null;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.single_button_activity);
-
-        findViewById(R.id.button).setOnClickListener((v) -> {
-            AssistStructure structure = getIntent().getParcelableExtra(
-                    AutofillManager.EXTRA_ASSIST_STRUCTURE);
-            if (structure != null) {
-                Parcelable result;
-                if (sResponse != null) {
-                    result = sResponse.asFillResponse(/* contexts= */ null,
-                            (id) -> Helper.findNodeByResourceId(structure, id));
-                } else if (sDataset != null) {
-                    result = sDataset.asDataset(
-                            (id) -> Helper.findNodeByResourceId(structure, id));
-                } else {
-                    throw new IllegalStateException("no dataset or response");
-                }
-
-                // Pass on the auth result
-                Intent intent = new Intent();
-                intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, result);
-                setResult(RESULT_OK, intent);
-            }
-
-            // Done
-            finish();
-        });
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MaxVisibleDatasetsRule.java b/tests/autofillservice/src/android/autofillservice/cts/MaxVisibleDatasetsRule.java
deleted file mode 100644
index 8a397d9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MaxVisibleDatasetsRule.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that improves autofill-related environment by:
- *
- * <ol>
- *   <li>Setting max_visible_datasets before and after test.
- * </ol>
- */
-public final class MaxVisibleDatasetsRule implements TestRule {
-
-    private static final String TAG = MaxVisibleDatasetsRule.class.getSimpleName();
-
-    private final int mMaxNumber;
-
-    /**
-     * Creates a MaxVisibleDatasetsRule with given datasets values.
-     *
-     * @param maxNumber The desired max_visible_datasets value for a test,
-     * after the test it will be replaced by the original value
-     */
-    public MaxVisibleDatasetsRule(int maxNumber) {
-        mMaxNumber = maxNumber;
-    }
-
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                final int original = Helper.getMaxVisibleDatasets();
-                Helper.setMaxVisibleDatasets(mMaxNumber);
-                try {
-                    base.evaluate();
-                } finally {
-                    Helper.setMaxVisibleDatasets(original);
-                }
-            }
-        };
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiScreenDifferentActivitiesTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiScreenDifferentActivitiesTest.java
deleted file mode 100644
index 122cfc5..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiScreenDifferentActivitiesTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.PreSimpleSaveActivity.ID_PRE_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.ComponentName;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.UiObject2;
-
-import org.junit.Test;
-
-@AppModeFull(reason = "Service-specific test")
-public class MultiScreenDifferentActivitiesTest
-        extends AutoFillServiceTestCase.ManualActivityLaunch {
-
-    @Test
-    public void testActivityNotDelayedIsNotMerged() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger autofill on 1st activity, without using FLAG_DELAY_SAVE
-        final PreSimpleSaveActivity activity1 = startPreSimpleSaveActivity();
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_PRE_INPUT)
-                .build());
-
-        activity1.syncRunOnUiThread(() -> activity1.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger autofill on 2nd activity
-        final SimpleSaveActivity activity2 = startSimpleSaveActivity();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT)
-                .build());
-        activity2.syncRunOnUiThread(() -> activity2.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        activity2.syncRunOnUiThread(() -> {
-            activity2.mInput.setText("ID");
-            activity2.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Make sure only second request is available
-        assertThat(saveRequest.contexts).hasSize(1);
-
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
-    }
-
-    @Test
-    public void testDelayedActivityIsMerged() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger autofill on 1st activity, usingFLAG_DELAY_SAVE
-        final PreSimpleSaveActivity activity1 = startPreSimpleSaveActivity();
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_PRE_INPUT)
-                .build());
-
-        activity1.syncRunOnUiThread(() -> activity1.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Fill field but don't finish session yet
-        activity1.syncRunOnUiThread(() -> {
-            activity1.mPreInput.setText("PRE");
-        });
-
-        // Trigger autofill on 2nd activity
-        final SimpleSaveActivity activity2 = startSimpleSaveActivity();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT)
-                .build());
-        activity2.syncRunOnUiThread(() -> activity2.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        activity2.syncRunOnUiThread(() -> {
-            activity2.mInput.setText("ID");
-            activity2.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Make sure both requests are available
-        assertThat(saveRequest.contexts).hasSize(2);
-
-        // Assert 1st request
-        final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure();
-        assertWithMessage("no structure for 1st activity").that(structure1).isNotNull();
-        assertTextAndValue(findNodeByResourceId(structure1, ID_PRE_INPUT), "PRE");
-        assertThat(findNodeByResourceId(structure1, ID_INPUT)).isNull();
-        final ComponentName component1 = structure1.getActivityComponent();
-        assertThat(component1).isEqualTo(activity1.getComponentName());
-
-        // Assert 2nd request
-        final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure();
-        assertWithMessage("no structure for 2nd activity").that(structure2).isNotNull();
-        assertThat(findNodeByResourceId(structure2, ID_PRE_INPUT)).isNull();
-        assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "ID");
-        final ComponentName component2 = structure2.getActivityComponent();
-        assertThat(component2).isEqualTo(activity2.getComponentName());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiScreenLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiScreenLoginTest.java
deleted file mode 100644
index ca0a2d2..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiScreenLoginTest.java
+++ /dev/null
@@ -1,422 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.CustomDescriptionHelper.newCustomDescriptionWithUsernameAndPassword;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.view.autofill.AutofillId;
-
-import org.junit.Test;
-
-import java.util.regex.Pattern;
-
-/**
- * Test case for the senario where a login screen is split in multiple activities.
- */
-@AppModeFull(reason = "Service-specific test")
-public class MultiScreenLoginTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<UsernameOnlyActivity> {
-
-    private static final String TAG = "MultiScreenLoginTest";
-    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
-
-    private UsernameOnlyActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<UsernameOnlyActivity> getActivityRule() {
-        return new AutofillActivityTestRule<UsernameOnlyActivity>(UsernameOnlyActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    /**
-     * Tests the "traditional" scenario where the service must save each field (username and
-     * password) separately.
-     */
-    @Test
-    public void testSaveEachFieldSeparately() throws Exception {
-        // Set service
-        enableService();
-
-        // First handle username...
-
-        // Set expectations.
-        final Bundle clientState1 = new Bundle();
-        clientState1.putString("first", "one");
-        clientState1.putString("last", "one");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
-                .setExtras(clientState1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusOnUsername();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save...
-        mActivity.setUsername("dude");
-        mActivity.next();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME);
-
-        // ..and assert results
-        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest1.structure, ID_USERNAME), "dude");
-        assertThat(saveRequest1.data.getString("first")).isEqualTo("one");
-        assertThat(saveRequest1.data.getString("last")).isEqualTo("one");
-
-        // ...now rinse and repeat for password
-
-        // Get the activity
-        final PasswordOnlyActivity activity2 = AutofillTestWatcher
-                .getActivity(PasswordOnlyActivity.class);
-
-        // Set expectations.
-        final Bundle clientState2 = new Bundle();
-        clientState2.putString("second", "two");
-        clientState2.putString("last", "two");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD)
-                .setExtras(clientState2)
-                .build());
-
-        // Trigger autofill
-        activity2.focusOnPassword();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts.size()).isEqualTo(1);
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save...
-        activity2.setPassword("sweet");
-        activity2.login();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // ..and assert results
-        final SaveRequest saveRequest2 = sReplier.getNextSaveRequest();
-        assertThat(saveRequest2.data.getString("first")).isNull();
-        assertThat(saveRequest2.data.getString("second")).isEqualTo("two");
-        assertThat(saveRequest2.data.getString("last")).isEqualTo("two");
-        assertTextAndValue(findNodeByResourceId(saveRequest2.structure, ID_PASSWORD), "sweet");
-    }
-
-    /**
-     * Tests the new scenario introudced on Q where the service can set a multi-screen session,
-     * with the service setting the client state just in the first request (so its passed to both
-     * the second fill request and the save request.
-     */
-    @Test
-    public void testSaveBothFieldsAtOnceNoClientStateOnSecondRequest() throws Exception {
-        // Set service
-        enableService();
-
-        // First handle username...
-
-        // Set expectations.
-        final Bundle clientState1 = new Bundle();
-        clientState1.putString("first", "one");
-        clientState1.putString("last", "one");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
-                .setExtras(clientState1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusOnUsername();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
-        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
-        assertThat(component1).isEqualTo(mActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger what would be save...
-        mActivity.setUsername("dude");
-        mActivity.next();
-        mUiBot.assertSaveNotShowing();
-
-        // ...now rinse and repeat for password
-
-        // Get the activity
-        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
-                .getActivity(PasswordOnlyActivity.class);
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                        ID_PASSWORD)
-                .build());
-
-        // Trigger autofill
-        passwordActivity.focusOnPassword();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
-        // Client state should come from 1st request
-        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
-        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
-
-        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
-        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save...
-        passwordActivity.setPassword("sweet");
-        passwordActivity.login();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
-
-        // ..and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        // Client state should come from 1st request
-        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
-        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
-
-        assertThat(saveRequest.contexts.size()).isEqualTo(2);
-
-        // Username is set in the 1st context
-        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
-        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
-        assertTextAndValue(findNodeByResourceId(previousStructure, ID_USERNAME), "dude");
-        final ComponentName componentPrevious = previousStructure.getActivityComponent();
-        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
-
-        // Password is set in the 2nd context
-        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
-        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
-        assertTextAndValue(findNodeByResourceId(currentStructure, ID_PASSWORD), "sweet");
-        final ComponentName componentCurrent = currentStructure.getActivityComponent();
-        assertThat(componentCurrent).isEqualTo(passwordActivity.getComponentName());
-    }
-
-    /**
-     * Tests the new scenario introudced on Q where the service can set a multi-screen session,
-     * with the service setting the client state just on both requests (so the 1st client state is
-     * passed to the 2nd request, and the 2nd client state is passed to the save request).
-     */
-    @Test
-    public void testSaveBothFieldsAtOnceWithClientStateOnBothRequests() throws Exception {
-        // Set service
-        enableService();
-
-        // First handle username...
-
-        // Set expectations.
-        final Bundle clientState1 = new Bundle();
-        clientState1.putString("first", "one");
-        clientState1.putString("last", "one");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
-                .setExtras(clientState1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusOnUsername();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
-        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
-        assertThat(component1).isEqualTo(mActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger what would be save...
-        mActivity.setUsername("dude");
-        mActivity.next();
-        mUiBot.assertSaveNotShowing();
-
-        // ...now rinse and repeat for password
-
-        // Get the activity
-        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
-                .getActivity(PasswordOnlyActivity.class);
-
-        // Set expectations.
-        final Bundle clientState2 = new Bundle();
-        clientState2.putString("second", "two");
-        clientState2.putString("last", "two");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                        ID_PASSWORD)
-                .setExtras(clientState2)
-                .build());
-
-        // Trigger autofill
-        passwordActivity.focusOnPassword();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
-        // Client state on 2nd request should come from previous (1st) request
-        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
-        assertThat(fillRequest2.data.getString("second")).isNull();
-        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
-
-        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
-        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save...
-        passwordActivity.setPassword("sweet");
-        passwordActivity.login();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
-
-        // ..and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        // Client state on save request should come from last (2nd) request
-        assertThat(saveRequest.data.getString("first")).isNull();
-        assertThat(saveRequest.data.getString("second")).isEqualTo("two");
-        assertThat(saveRequest.data.getString("last")).isEqualTo("two");
-
-        assertThat(saveRequest.contexts.size()).isEqualTo(2);
-
-        // Username is set in the 1st context
-        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
-        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
-        assertTextAndValue(findNodeByResourceId(previousStructure, ID_USERNAME), "dude");
-        final ComponentName componentPrevious = previousStructure.getActivityComponent();
-        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
-
-        // Password is set in the 2nd context
-        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
-        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
-        assertTextAndValue(findNodeByResourceId(currentStructure, ID_PASSWORD), "sweet");
-        final ComponentName componentCurrent = currentStructure.getActivityComponent();
-        assertThat(componentCurrent).isEqualTo(passwordActivity.getComponentName());
-    }
-
-    @Test
-    public void testSaveBothFieldsCustomDescription_differentIds() throws Exception {
-        saveBothFieldsCustomDescription(false);
-    }
-
-    @Test
-    public void testSaveBothFieldsCustomDescription_sameIds() throws Exception {
-        saveBothFieldsCustomDescription(true);
-    }
-
-    private void saveBothFieldsCustomDescription(boolean sameAutofillId) throws Exception {
-        // Set service
-        enableService();
-
-        // Set ids
-        final AutofillId appUsernameId = mActivity.getUsernameAutofillId();
-        final AutofillId appPasswordId = sameAutofillId ? appUsernameId
-                : mActivity.getAutofillManager().getNextAutofillId();
-        mActivity.setPasswordAutofillId(appPasswordId);
-        Log.d(TAG, "App: usernameId=" + appUsernameId + ", passwordId=" + appPasswordId);
-
-        // First handle username...
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusOnUsername();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
-        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
-        assertThat(component1).isEqualTo(mActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger what would be save...
-        mActivity.setUsername("dude");
-        mActivity.next();
-        mUiBot.assertSaveNotShowing();
-
-        // ...now rinse and repeat for password
-
-        // Get the activity
-        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
-                .getActivity(PasswordOnlyActivity.class);
-
-        // Must get AutofillIds from FillRequest, as they contain the proper session ids
-        final AutofillId svcUsernameId = findAutofillIdByResourceId(fillRequest1.contexts.get(0),
-                ID_USERNAME);
-        Log.d(TAG, "Service: usernameId=" + svcUsernameId);
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setVisitor((contexts, builder) -> {
-                    final AutofillId svcPasswordId =
-                            findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD);
-                    Log.d(TAG, "Service: passwordId=" + svcPasswordId);
-                    final CharSequenceTransformation usernameTrans =
-                            new CharSequenceTransformation.Builder(svcUsernameId, MATCH_ALL, "$1")
-                            .build();
-                    final CharSequenceTransformation passwordTrans =
-                            new CharSequenceTransformation.Builder(svcPasswordId, MATCH_ALL, "$1")
-                            .build();
-                    builder.setSaveInfo(new SaveInfo.Builder(
-                            SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                            new AutofillId[] {svcPasswordId})
-                            .setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
-                                    .addChild(R.id.username, usernameTrans)
-                                    .addChild(R.id.password, passwordTrans)
-                                    .build())
-                            .build());
-                })
-                .build());
-
-        // Trigger autofill
-        passwordActivity.focusOnPassword();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
-
-        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
-        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save...
-        passwordActivity.setPassword("sweet");
-        passwordActivity.login();
-
-        // ...and assert UI
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(
-                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME,
-                SAVE_DATA_TYPE_PASSWORD);
-
-        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
-        mUiBot.assertChildText(saveUi, ID_USERNAME, "dude");
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
-        mUiBot.assertChildText(saveUi, ID_PASSWORD, "sweet");
-    }
-
-    // TODO(b/113281366): add test cases for more scenarios such as:
-    // - make sure that activity not marked with keepAlive is not sent in the 2nd request
-    // - somehow verify that the first activity's session is gone
-    // - WebView
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java
deleted file mode 100644
index 0fd0c83..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java
+++ /dev/null
@@ -1,62 +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.autofillservice.cts;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Empty activity that allows to be put in different split window.
- */
-public class MultiWindowEmptyActivity extends EmptyActivity {
-
-    private static MultiWindowEmptyActivity sLastInstance;
-    private static CountDownLatch sLastInstanceLatch;
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        sLastInstance = this;
-        if (sLastInstanceLatch != null) {
-            sLastInstanceLatch.countDown();
-        }
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        if (hasFocus) {
-            if (sLastInstanceLatch != null) {
-                sLastInstanceLatch.countDown();
-            }
-        }
-    }
-
-    public static void expectNewInstance(boolean waitWindowFocus) {
-        sLastInstanceLatch = new CountDownLatch(waitWindowFocus ? 2 : 1);
-    }
-
-    public static MultiWindowEmptyActivity waitNewInstance() throws InterruptedException {
-        if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
-                TimeUnit.MILLISECONDS)) {
-            throw new RetryableException("New MultiWindowLoginActivity didn't start",
-                    Timeouts.ACTIVITY_RESURRECTION);
-        }
-        sLastInstanceLatch = null;
-        return sLastInstance;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java
deleted file mode 100644
index 9512591..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java
+++ /dev/null
@@ -1,62 +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.autofillservice.cts;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity that allows capture recreated instance for testing multi window scenarios.
- */
-public class MultiWindowLoginActivity extends LoginActivity {
-
-    private static MultiWindowLoginActivity sLastInstance;
-    private static CountDownLatch sLastInstanceLatch;
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        sLastInstance = this;
-        if (sLastInstanceLatch != null) {
-            sLastInstanceLatch.countDown();
-        }
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        if (hasFocus) {
-            if (sLastInstanceLatch != null) {
-                sLastInstanceLatch.countDown();
-            }
-        }
-    }
-
-    public static void expectNewInstance(boolean waitWindowFocus) {
-        sLastInstanceLatch = new CountDownLatch(waitWindowFocus ? 2 : 1);
-    }
-
-    public static MultiWindowLoginActivity waitNewInstance() throws InterruptedException {
-        if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
-                TimeUnit.MILLISECONDS)) {
-            throw new RetryableException("New MultiWindowLoginActivity didn't start",
-                    Timeouts.ACTIVITY_RESURRECTION);
-        }
-        sLastInstanceLatch = null;
-        return sLastInstance;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
index e7f26e2..1bd6196 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
@@ -16,8 +16,8 @@
 package android.autofillservice.cts;
 
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 import static com.android.compatibility.common.util.ShellUtils.tap;
@@ -28,6 +28,13 @@
 
 import android.app.Activity;
 import android.app.ActivityTaskManager;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.MultiWindowEmptyActivity;
+import android.autofillservice.cts.activities.MultiWindowLoginActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
 import android.content.Intent;
 import android.platform.test.annotations.AppModeFull;
 import android.view.View;
@@ -68,7 +75,7 @@
     @Before
     public void setup() {
         assumeTrue("Skipping test: no split multi-window support",
-                ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
+                ActivityTaskManager.supportsSplitScreenMultiWindow(mActivity));
     }
 
     /**
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
deleted file mode 100644
index ad08fd3..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.FragmentContainerActivity.FRAGMENT_TAG;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.autofill.AutofillValue;
-import android.widget.EditText;
-
-import org.junit.Test;
-
-public class MultipleFragmentLoginTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<FragmentContainerActivity> {
-
-    private static final String LOG_TAG = "MultipleFragmentLoginTest";
-
-    private FragmentContainerActivity mActivity;
-    private EditText mEditText1;
-    private EditText mEditText2;
-
-    @Override
-    protected AutofillActivityTestRule<FragmentContainerActivity> getActivityRule() {
-        return new AutofillActivityTestRule<FragmentContainerActivity>(
-                FragmentContainerActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-                mEditText1 = mActivity.findViewById(R.id.editText1);
-                mEditText2 = mActivity.findViewById(R.id.editText2);
-            }
-        };
-    }
-
-    @Test
-    public void loginOnTwoFragments() throws Exception {
-        enableService();
-
-        Bundle clientState = new Bundle();
-        clientState.putString("key", "value1");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField("editText1", "editText1-autofilled")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .setExtras(clientState)
-                .build());
-
-        // Trigger autofill on editText2
-        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
-
-        final InstrumentedAutoFillService.FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.data).isNull();
-
-        mUiBot.assertNoDatasetsEver(); // UI is only shown on editText1
-
-        mActivity.setRootContainerFocusable(false);
-
-        final AssistStructure structure = fillRequest1.contexts.get(0).getStructure();
-        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
-        assertThat(findNodeByResourceId(structure, "editText1")).isNotNull();
-        assertThat(findNodeByResourceId(structure, "editText2")).isNotNull();
-        assertThat(findNodeByResourceId(structure, "editText3")).isNull();
-        assertThat(findNodeByResourceId(structure, "editText4")).isNull();
-        assertThat(findNodeByResourceId(structure, "editText5")).isNull();
-
-        // Wait until autofill has been applied
-        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
-        mUiBot.selectDataset("dataset1");
-        mUiBot.assertShownByText("editText1-autofilled");
-
-        // Manually fill view
-        mActivity.syncRunOnUiThread(() -> mEditText2.setText("editText2-manually-filled"));
-
-        // Replacing the fragment focused a previously unknown view which triggers a new
-        // partition
-        clientState.putString("key", "value2");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField("editText3", "editText3-autofilled")
-                        .setField("editText4", "editText4-autofilled")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText2", "editText5")
-                .setExtras(clientState)
-                .build());
-
-        Log.i(LOG_TAG, "Switching Fragments");
-        mActivity.syncRunOnUiThread(
-                () -> mActivity.getFragmentManager().beginTransaction().replace(
-                        R.id.rootContainer, new FragmentWithMoreEditTexts(),
-                        FRAGMENT_TAG).commitNow());
-        EditText editText5 = mActivity.findViewById(R.id.editText5);
-        final InstrumentedAutoFillService.FillRequest fillRequest2 = sReplier.getNextFillRequest();
-
-        // The fillRequest should have a fillContext for each partition. The first partition
-        // should be filled in
-        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
-
-        assertThat(fillRequest2.data.getString("key")).isEqualTo("value1");
-
-        final AssistStructure structure1 = fillRequest2.contexts.get(0).getStructure();
-        ViewNode editText1Node = findNodeByResourceId(structure1, "editText1");
-        // The actual value in the structure is not updated in FillRequest-contexts, but the
-        // autofill value is. For text views in SaveRequest both are updated, but this is the
-        // only exception.
-        assertThat(editText1Node.getAutofillValue()).isEqualTo(
-                AutofillValue.forText("editText1-autofilled"));
-
-        ViewNode editText2Node = findNodeByResourceId(structure1, "editText2");
-        // Manually filled fields are not send to onFill. They appear in onSave if they are set
-        // as saveable fields.
-        assertThat(editText2Node.getText().toString()).isEqualTo("");
-
-        assertThat(findNodeByResourceId(structure1, "editText3")).isNull();
-        assertThat(findNodeByResourceId(structure1, "editText4")).isNull();
-        assertThat(findNodeByResourceId(structure1, "editText5")).isNull();
-
-        final AssistStructure structure2 = fillRequest2.contexts.get(1).getStructure();
-
-        assertThat(findNodeByResourceId(structure2, "editText1")).isNull();
-        assertThat(findNodeByResourceId(structure2, "editText2")).isNull();
-        assertThat(findNodeByResourceId(structure2, "editText3")).isNotNull();
-        assertThat(findNodeByResourceId(structure2, "editText4")).isNotNull();
-        assertThat(findNodeByResourceId(structure2, "editText5")).isNotNull();
-
-        // Wait until autofill has been applied
-        mUiBot.selectDataset("dataset2");
-        mUiBot.assertShownByText("editText3-autofilled");
-        mUiBot.assertShownByText("editText4-autofilled");
-
-        // Manually fill view
-        mActivity.syncRunOnUiThread(() -> editText5.setText("editText5-manually-filled"));
-
-        // Finish activity and save data
-        mActivity.finish();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-
-        // The saveRequest should have a fillContext for each partition with all the data
-        final InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertThat(saveRequest.contexts.size()).isEqualTo(2);
-
-        assertThat(saveRequest.data.getString("key")).isEqualTo("value2");
-
-        final AssistStructure saveStructure1 = saveRequest.contexts.get(0).getStructure();
-        editText1Node = findNodeByResourceId(saveStructure1, "editText1");
-        assertThat(editText1Node.getText().toString()).isEqualTo("editText1-autofilled");
-
-        editText2Node = findNodeByResourceId(saveStructure1, "editText2");
-        assertThat(editText2Node.getText().toString()).isEqualTo("editText2-manually-filled");
-
-        assertThat(findNodeByResourceId(saveStructure1, "editText3")).isNull();
-        assertThat(findNodeByResourceId(saveStructure1, "editText4")).isNull();
-        assertThat(findNodeByResourceId(saveStructure1, "editText5")).isNull();
-
-        final AssistStructure saveStructure2 = saveRequest.contexts.get(1).getStructure();
-        assertThat(findNodeByResourceId(saveStructure2, "editText1")).isNull();
-        assertThat(findNodeByResourceId(saveStructure2, "editText2")).isNull();
-
-        ViewNode editText3Node = findNodeByResourceId(saveStructure2, "editText3");
-        assertThat(editText3Node.getText().toString()).isEqualTo("editText3-autofilled");
-
-        ViewNode editText4Node = findNodeByResourceId(saveStructure2, "editText4");
-        assertThat(editText4Node.getText().toString()).isEqualTo("editText4-autofilled");
-
-        ViewNode editText5Node = findNodeByResourceId(saveStructure2, "editText5");
-        assertThat(editText5Node.getText().toString()).isEqualTo("editText5-manually-filled");
-    }
-
-    @Test
-    public void uiDismissedWhenNonSavableFragmentIsGone() throws Exception {
-        uiDismissedWhenFragmentIsGoneText(false);
-    }
-
-    @Test
-    public void uiDismissedWhenSavableFragmentIsGone() throws Exception {
-        uiDismissedWhenFragmentIsGoneText(true);
-    }
-
-    private void uiDismissedWhenFragmentIsGoneText(boolean savable) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField("editText1", "whatever")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build());
-        if (savable) {
-            response.setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText2");
-        }
-
-        sReplier.addResponse(response.build());
-
-        // Trigger autofill on editText2
-        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver(); // UI is only shown on editText1
-
-        mActivity.setRootContainerFocusable(false);
-
-        // Check UI is shown, but don't select it.
-        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
-        mUiBot.assertDatasets("dataset1");
-
-        // Switch fragments
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.syncRunOnUiThread(
-                () -> mActivity.getFragmentManager().beginTransaction().replace(
-                        R.id.rootContainer, new FragmentWithMoreEditTexts(),
-                        FRAGMENT_TAG).commitNow());
-        // Make sure UI is gone.
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasets();
-    }
-
-    // TODO: add similar tests for fragment with virtual view
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
deleted file mode 100644
index 5af2762..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.widget.RadioGroup;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link android.widget.RadioGroup.OnCheckedChangeListener} used to assert an
- * {@link RadioGroup} was auto-filled properly.
- */
-final class MultipleTimesRadioGroupListener implements RadioGroup.OnCheckedChangeListener {
-    private final String mName;
-    private final CountDownLatch mLatch;
-    private final RadioGroup mRadioGroup;
-    private final int mExpected;
-
-    MultipleTimesRadioGroupListener(String name, int times, RadioGroup radioGroup,
-            int expectedAutoFilledValue) {
-        mName = name;
-        mRadioGroup = radioGroup;
-        mExpected = expectedAutoFilledValue;
-        mLatch = new CountDownLatch(times);
-    }
-
-    @Override
-    public void onCheckedChanged(RadioGroup group, int checkedId) {
-        mLatch.countDown();
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), mName)
-            .that(set).isTrue();
-        final int actual = mRadioGroup.getAutofillValue().getListValue();
-        assertWithMessage("Wrong auto-fill value on RadioGroup %s", mName)
-            .that(actual).isEqualTo(mExpected);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java
deleted file mode 100644
index a841fef..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.widget.EditText;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link TextWatcher} used to assert a {@link EditText} was set multiple times.
- */
-class MultipleTimesTextWatcher implements TextWatcher {
-    private static final String TAG = "MultipleTimesTextWatcher";
-
-    private final String mName;
-    private final CountDownLatch mLatch;
-    private final EditText mEditText;
-    private final CharSequence mExpected;
-
-    MultipleTimesTextWatcher(String name, int times, EditText editText,
-            CharSequence expectedAutofillValue) {
-        this.mName = name;
-        this.mEditText = editText;
-        this.mExpected = expectedAutofillValue;
-        this.mLatch = new CountDownLatch(times);
-    }
-
-    @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-    }
-
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-        Log.v(TAG, "onTextChanged(" + mLatch.getCount() + "): " + mName + " = " + s);
-        mLatch.countDown();
-    }
-
-    @Override
-    public void afterTextChanged(Editable s) {
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        if (!set) {
-            throw new RetryableException(FILL_TIMEOUT, "Timeout (%s ms) on EditText %s",
-                    FILL_TIMEOUT.ms(), mName);
-        }
-        final String actual = mEditText.getText().toString();
-        assertWithMessage("Wrong auto-fill value on EditText %s", mName)
-                .that(actual).isEqualTo(mExpected.toString());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java
deleted file mode 100644
index 2519aec..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.widget.TimePicker;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@OnDateChangedListener} used to assert a {@link TimePicker} was auto-filled properly.
- */
-final class MultipleTimesTimeListener implements TimePicker.OnTimeChangedListener {
-    private final String name;
-    private final CountDownLatch latch;
-    private final TimePicker timePicker;
-    private final int expectedHour;
-    private final int expectedMinute;
-
-    MultipleTimesTimeListener(String name, int times, TimePicker timePicker, int expectedHour,
-            int expectedMinute) {
-        this.name = name;
-        this.timePicker = timePicker;
-        this.expectedHour = expectedHour;
-        this.expectedMinute = expectedMinute;
-        this.latch = new CountDownLatch(times);
-    }
-
-    @Override
-    public void onTimeChanged(TimePicker view, int hour, int minute) {
-        latch.countDown();
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on TimePicker %s", FILL_TIMEOUT.ms(), name)
-                .that(set).isTrue();
-        assertWithMessage("Wrong hour on TimePicker %s", name)
-                .that(timePicker.getHour()).isEqualTo(expectedHour);
-        assertWithMessage("Wrong minute on TimePicker %s", name)
-                .that(timePicker.getMinute()).isEqualTo(expectedMinute);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java b/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java
index 8d63fcd..ce37c23 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java
@@ -16,11 +16,11 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.GridActivity.ID_L1C1;
-import static android.autofillservice.cts.GridActivity.ID_L1C2;
-import static android.autofillservice.cts.Helper.assertEqualsIgnoreSession;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C2;
+import static android.autofillservice.cts.testcore.Helper.assertEqualsIgnoreSession;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -29,10 +29,12 @@
 
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.GridActivity.FillExpectation;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.activities.GridActivity.FillExpectation;
+import android.autofillservice.cts.commontests.AbstractGridActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
 import android.service.autofill.FillContext;
 import android.support.test.uiautomator.UiObject2;
 import android.util.Log;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
deleted file mode 100644
index 7a09dbf..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.callbackEventAsString;
-import static android.autofillservice.cts.Timeouts.CALLBACK_NOT_CALLED_TIMEOUT_MS;
-import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.util.Log;
-import android.view.View;
-import android.view.autofill.AutofillManager.AutofillCallback;
-
-import com.android.compatibility.common.util.RetryableException;
-import com.android.compatibility.common.util.Timeout;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link AutofillCallback} used to recover events during tests.
- */
-public final class MyAutofillCallback extends AutofillCallback {
-
-    private static final String TAG = "MyAutofillCallback";
-    private final BlockingQueue<MyEvent> mEvents = new LinkedBlockingQueue<>();
-
-    public static final Timeout MY_TIMEOUT = CONNECTION_TIMEOUT;
-
-    // We must handle all requests in a separate thread as the service's main thread is the also
-    // the UI thread of the test process and we don't want to hose it in case of failures here
-    private static final HandlerThread sMyThread = new HandlerThread("MyCallbackThread");
-    private final Handler mHandler;
-
-    static {
-        Log.i(TAG, "Starting thread " + sMyThread);
-        sMyThread.start();
-    }
-
-    MyAutofillCallback() {
-        mHandler = Handler.createAsync(sMyThread.getLooper());
-    }
-
-    @Override
-    public void onAutofillEvent(View view, int event) {
-        mHandler.post(() -> offer(new MyEvent(view, event)));
-    }
-
-    @Override
-    public void onAutofillEvent(View view, int childId, int event) {
-        mHandler.post(() -> offer(new MyEvent(view, childId, event)));
-    }
-
-    private void offer(MyEvent event) {
-        Log.v(TAG, "offer: " + event);
-        Helper.offer(mEvents, event, MY_TIMEOUT.ms());
-    }
-
-    /**
-     * Gets the next available event or fail if it times out.
-     */
-    public MyEvent getEvent() throws InterruptedException {
-        final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        if (event == null) {
-            throw new RetryableException(CONNECTION_TIMEOUT, "no event");
-        }
-        return event;
-    }
-
-    /**
-     * Assert no more events were received.
-     */
-    public void assertNotCalled() throws InterruptedException {
-        final MyEvent event = mEvents.poll(CALLBACK_NOT_CALLED_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        if (event != null) {
-            // Not retryable.
-            throw new IllegalStateException("should not have received " + event);
-        }
-    }
-
-    /**
-     * Used to assert there is no event left behind.
-     */
-    public void assertNumberUnhandledEvents(int expected) {
-        assertWithMessage("Invalid number of events left: %s", mEvents).that(mEvents.size())
-                .isEqualTo(expected);
-    }
-
-    /**
-     * Convenience method to assert an UI shown event for the given view was received.
-     */
-    public MyEvent assertUiShownEvent(View expectedView) throws InterruptedException {
-        final MyEvent event = getEvent();
-        assertWithMessage("Invalid type on event %s", event).that(event.event)
-                .isEqualTo(EVENT_INPUT_SHOWN);
-        assertWithMessage("Invalid view on event %s", event).that(event.view)
-            .isSameAs(expectedView);
-        return event;
-    }
-
-    /**
-     * Convenience method to assert an UI shown event for the given virtual view was received.
-     */
-    public void assertUiShownEvent(View expectedView, int expectedChildId)
-            throws InterruptedException {
-        final MyEvent event = assertUiShownEvent(expectedView);
-        assertWithMessage("Invalid child on event %s", event).that(event.childId)
-            .isEqualTo(expectedChildId);
-    }
-
-    /**
-     * Convenience method to assert an UI shown event a virtual view was received.
-     *
-     * @return virtual child id
-     */
-    public int assertUiShownEventForVirtualChild(View expectedView) throws InterruptedException {
-        final MyEvent event = assertUiShownEvent(expectedView);
-        return event.childId;
-    }
-
-    /**
-     * Convenience method to assert an UI hidden event for the given view was received.
-     */
-    public MyEvent assertUiHiddenEvent(View expectedView) throws InterruptedException {
-        final MyEvent event = getEvent();
-        assertWithMessage("Invalid type on event %s", event).that(event.event)
-                .isEqualTo(EVENT_INPUT_HIDDEN);
-        assertWithMessage("Invalid view on event %s", event).that(event.view)
-                .isSameAs(expectedView);
-        return event;
-    }
-
-    /**
-     * Convenience method to assert an UI hidden event for the given view was received.
-     */
-    public void assertUiHiddenEvent(View expectedView, int expectedChildId)
-            throws InterruptedException {
-        final MyEvent event = assertUiHiddenEvent(expectedView);
-        assertWithMessage("Invalid child on event %s", event).that(event.childId)
-                .isEqualTo(expectedChildId);
-    }
-
-    /**
-     * Convenience method to assert an UI unavailable event for the given view was received.
-     */
-    public MyEvent assertUiUnavailableEvent(View expectedView) throws InterruptedException {
-        final MyEvent event = getEvent();
-        assertWithMessage("Invalid type on event %s", event).that(event.event)
-                .isEqualTo(EVENT_INPUT_UNAVAILABLE);
-        assertWithMessage("Invalid view on event %s", event).that(event.view)
-                .isSameAs(expectedView);
-        return event;
-    }
-
-    /**
-     * Convenience method to assert an UI unavailable event for the given view was received.
-     */
-    public void assertUiUnavailableEvent(View expectedView, int expectedChildId)
-            throws InterruptedException {
-        final MyEvent event = assertUiUnavailableEvent(expectedView);
-        assertWithMessage("Invalid child on event %s", event).that(event.childId)
-                .isEqualTo(expectedChildId);
-    }
-
-    private static final class MyEvent {
-        public final View view;
-        public final int childId;
-        public final int event;
-
-        MyEvent(View view, int event) {
-            this(view, View.NO_ID, event);
-        }
-
-        MyEvent(View view, int childId, int event) {
-            this.view = view;
-            this.childId = childId;
-            this.event = event;
-        }
-
-        @Override
-        public String toString() {
-            return callbackEventAsString(event) + ": " + view + " (childId: " + childId + ")";
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillId.java b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillId.java
deleted file mode 100644
index 830f322..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillId.java
+++ /dev/null
@@ -1,80 +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.autofillservice.cts;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.autofill.AutofillId;
-
-public final class MyAutofillId implements Parcelable {
-
-    private final AutofillId mId;
-
-    public MyAutofillId(AutofillId id) {
-        mId = id;
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((mId == null) ? 0 : mId.hashCode());
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) return true;
-        if (obj == null) return false;
-        if (getClass() != obj.getClass()) return false;
-        MyAutofillId other = (MyAutofillId) obj;
-        if (mId == null) {
-            if (other.mId != null) return false;
-        } else if (!mId.equals(other.mId)) {
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public String toString() {
-        return mId.toString();
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeParcelable(mId, flags);
-    }
-
-    public static final Creator<MyAutofillId> CREATOR = new Creator<MyAutofillId>() {
-
-        @Override
-        public MyAutofillId createFromParcel(Parcel source) {
-            return new MyAutofillId(source.readParcelable(null));
-        }
-
-        @Override
-        public MyAutofillId[] newArray(int size) {
-            return new MyAutofillId[size];
-        }
-    };
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyDrawable.java b/tests/autofillservice/src/android/autofillservice/cts/MyDrawable.java
deleted file mode 100644
index ada5a67..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MyDrawable.java
+++ /dev/null
@@ -1,102 +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.autofillservice.cts;
-
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class MyDrawable extends Drawable {
-
-    private static final String TAG = "MyDrawable";
-
-    private static CountDownLatch sLatch;
-    private static MyDrawable sInstance;
-
-    private static Rect sAutofilledBounds;
-
-    public MyDrawable() {
-        if (sInstance != null) {
-            throw new IllegalStateException("There can be only one!");
-        }
-        sInstance = this;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        if (sInstance != null && sAutofilledBounds == null) {
-            sAutofilledBounds = new Rect(getBounds());
-            Log.d(TAG, "Autofilled at " + sAutofilledBounds);
-            sLatch.countDown();
-        }
-    }
-
-    public static Rect getAutofilledBounds() throws InterruptedException {
-        if (sLatch == null) {
-            throw new AssertionError("sLatch should be not null");
-        }
-
-        if (!sLatch.await(Timeouts.FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
-            throw new RetryableException(Timeouts.FILL_TIMEOUT, "custom drawable not drawn");
-        }
-        return sAutofilledBounds;
-    }
-
-    /**
-     * Asserts the custom drawable is not drawn.
-     */
-    public static void assertDrawableNotDrawn() throws Exception {
-        if (sLatch == null) {
-            throw new AssertionError("sLatch should be not null");
-        }
-
-        if (sLatch.await(Timeouts.DRAWABLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-            throw new AssertionError("custom drawable is drawn");
-        }
-    }
-
-    public static void initStatus() {
-        sLatch = new CountDownLatch(1);
-        sInstance = null;
-        sAutofilledBounds = null;
-    }
-
-    public static void clearStatus() {
-        sLatch = null;
-        sInstance = null;
-        sAutofilledBounds = null;
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) {
-    }
-
-    @Override
-    public int getOpacity() {
-        return 0;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java b/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java
deleted file mode 100644
index 35317f9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-import android.webkit.JavascriptInterface;
-import android.webkit.WebView;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link WebView} used to assert contents were autofilled.
- */
-public class MyWebView extends WebView {
-
-    private static final String TAG = "MyWebView";
-
-    private FillExpectation mExpectation;
-
-    public MyWebView(Context context) {
-        super(context);
-        setJsHandler();
-        Log.d(TAG, "isAutofillEnabled() on constructor? " + isAutofillEnabled());
-    }
-
-    public MyWebView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setJsHandler();
-        Log.d(TAG, "isAutofillEnabled() on constructor? " + isAutofillEnabled());
-    }
-
-    public void expectAutofill(String username, String password) {
-        mExpectation = new FillExpectation(username, password);
-    }
-
-    public void assertAutofilled() throws Exception {
-        assertWithMessage("expectAutofill() not called").that(mExpectation).isNotNull();
-        mExpectation.assertUsernameCalled();
-        mExpectation.assertPasswordCalled();
-    }
-
-    private void setJsHandler() {
-        getSettings().setJavaScriptEnabled(true);
-        addJavascriptInterface(new JavascriptHandler(), "JsHandler");
-    }
-
-    boolean isAutofillEnabled() {
-        return getContext().getSystemService(AutofillManager.class).isEnabled();
-    }
-
-    private class FillExpectation {
-        private final CountDownLatch mUsernameLatch = new CountDownLatch(1);
-        private final CountDownLatch mPasswordLatch = new CountDownLatch(1);
-        private final String mExpectedUsername;
-        private final String mExpectedPassword;
-        private String mActualUsername;
-        private String mActualPassword;
-
-        FillExpectation(String username, String password) {
-            this.mExpectedUsername = username;
-            this.mExpectedPassword = password;
-        }
-
-        void setUsername(String username) {
-            mActualUsername = username;
-            mUsernameLatch.countDown();
-        }
-
-        void setPassword(String password) {
-            mActualPassword = password;
-            mPasswordLatch.countDown();
-        }
-
-        void assertUsernameCalled() throws Exception {
-            assertCalled(mUsernameLatch, "username");
-            assertWithMessage("Wrong value for username").that(mExpectation.mActualUsername)
-                .isEqualTo(mExpectation.mExpectedUsername);
-        }
-
-        void assertPasswordCalled() throws Exception {
-            assertCalled(mPasswordLatch, "password");
-            assertWithMessage("Wrong value for password").that(mExpectation.mActualPassword)
-                    .isEqualTo(mExpectation.mExpectedPassword);
-        }
-
-        private void assertCalled(CountDownLatch latch, String field) throws Exception {
-            if (!latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
-                throw new RetryableException(FILL_TIMEOUT, "%s not called", field);
-            }
-        }
-    }
-
-    private class JavascriptHandler {
-
-        @JavascriptInterface
-        public void onUsernameChanged(String username) {
-            Log.d(TAG, "onUsernameChanged():" + username);
-            if (mExpectation != null) {
-                mExpectation.setUsername(username);
-            }
-        }
-
-        @JavascriptInterface
-        public void onPasswordChanged(String password) {
-            Log.d(TAG, "onPasswordChanged():" + password);
-            if (mExpectation != null) {
-                mExpectation.setPassword(password);
-            }
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
deleted file mode 100644
index 43bb5f1..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.os.CancellationSignal;
-import android.service.autofill.AutofillService;
-import android.service.autofill.FillCallback;
-import android.service.autofill.FillRequest;
-import android.service.autofill.SaveCallback;
-import android.service.autofill.SaveRequest;
-import android.util.Log;
-
-/**
- * {@link AutofillService} implementation that does not do anything...
- */
-public class NoOpAutofillService extends AutofillService {
-
-    private static final String TAG = "NoOpAutofillService";
-
-    static final String SERVICE_NAME = NoOpAutofillService.class.getPackage().getName()
-            + "/." + NoOpAutofillService.class.getSimpleName();
-    static final String SERVICE_LABEL = "NoOpAutofillService";
-
-    @Override
-    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
-            FillCallback callback) {
-        Log.d(TAG, "onFillRequest()");
-    }
-
-    @Override
-    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
-        Log.d(TAG, "onFillResponse()");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/NonAutofillableActivity.java b/tests/autofillservice/src/android/autofillservice/cts/NonAutofillableActivity.java
deleted file mode 100644
index 3233cd4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/NonAutofillableActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-
-public final class NonAutofillableActivity extends AbstractAutoFillActivity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.non_autofillable_activity);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OnClickActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/OnClickActionTest.java
deleted file mode 100644
index c65f78c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OnClickActionTest.java
+++ /dev/null
@@ -1,190 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_HIDE;
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_PASSWORD_MASKED;
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_PASSWORD_PLAIN;
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_SHOW;
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_USERNAME_MASKED;
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_USERNAME_PLAIN;
-import static android.autofillservice.cts.CustomDescriptionHelper.newCustomDescriptionWithHiddenFields;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.FillContext;
-import android.service.autofill.OnClickAction;
-import android.service.autofill.VisibilitySetterAction;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillId;
-
-import org.junit.Test;
-
-import java.util.regex.Pattern;
-
-/**
- * Integration tests for the {@link OnClickAction} implementations.
- */
-@AppModeFull(reason = "Service-specific test")
-public class OnClickActionTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<SimpleSaveActivity> {
-
-    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
-
-    private SimpleSaveActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
-        return new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testHideAndShow() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId usernameId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-
-                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
-                            .Builder(usernameId, MATCH_ALL, "$1").build();
-                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
-                            .Builder(passwordId, MATCH_ALL, "$1").build();
-                    builder.setCustomDescription(newCustomDescriptionWithHiddenFields()
-                            .addChild(R.id.username_plain, usernameTrans)
-                            .addChild(R.id.password_plain, passwordTrans)
-                            .addOnClickAction(R.id.show, new VisibilitySetterAction
-                                    .Builder(R.id.hide, View.VISIBLE)
-                                    .setVisibility(R.id.show, View.GONE)
-                                    .setVisibility(R.id.username_plain, View.VISIBLE)
-                                    .setVisibility(R.id.password_plain, View.VISIBLE)
-                                    .setVisibility(R.id.username_masked, View.GONE)
-                                    .setVisibility(R.id.password_masked, View.GONE)
-                                    .build())
-                            .addOnClickAction(R.id.hide, new VisibilitySetterAction
-                                    .Builder(R.id.show, View.VISIBLE)
-                                    .setVisibility(R.id.hide, View.GONE)
-                                    .setVisibility(R.id.username_masked, View.VISIBLE)
-                                    .setVisibility(R.id.password_masked, View.VISIBLE)
-                                    .setVisibility(R.id.username_plain, View.GONE)
-                                    .setVisibility(R.id.password_plain, View.GONE)
-                                    .build())
-                            .build());
-                })
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("42");
-            mActivity.mPassword.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Assert initial UI is hidden the password.
-        final UiObject2 showButton = assertHidden(saveUi);
-
-        // Then tap SHOW and assert it's showing how
-        showButton.click();
-        final UiObject2 hideButton = assertShown(saveUi);
-
-        // Hide again
-        hideButton.click();
-        assertHidden(saveUi);
-
-        // Rinse-and repeat a couple times
-        showButton.click(); assertShown(saveUi);
-        hideButton.click(); assertHidden(saveUi);
-        showButton.click(); assertShown(saveUi);
-        hideButton.click(); assertHidden(saveUi);
-
-        // Then save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "108");
-    }
-
-    /**
-     * Asserts that the Save UI is in the hiding the password field, returning the {@code SHOW}
-     * button.
-     */
-    private UiObject2 assertHidden(UiObject2 saveUi) throws Exception {
-        // Username
-        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
-        mUiBot.assertChildText(saveUi, ID_USERNAME_MASKED, "****");
-        assertInvisible(saveUi, ID_USERNAME_PLAIN);
-
-        // Password
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_MASKED, "....");
-        assertInvisible(saveUi, ID_PASSWORD_PLAIN);
-
-        // Buttons
-        assertInvisible(saveUi, ID_HIDE);
-        return mUiBot.assertChildText(saveUi, ID_SHOW, "SHOW");
-    }
-
-    /**
-     * Asserts that the Save UI is in the showing the password field, returning the {@code HIDE}
-     * button.
-     */
-    private UiObject2 assertShown(UiObject2 saveUi) throws Exception {
-        // Username
-        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
-        mUiBot.assertChildText(saveUi, ID_USERNAME_PLAIN, "42");
-        assertInvisible(saveUi, ID_USERNAME_MASKED);
-
-        // Password
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_PLAIN, "108");
-        assertInvisible(saveUi, ID_PASSWORD_MASKED);
-
-        // Buttons
-        assertInvisible(saveUi, ID_SHOW);
-        return mUiBot.assertChildText(saveUi, ID_HIDE, "HIDE");
-    }
-
-    private void assertInvisible(UiObject2 saveUi, String resourceId) {
-        mUiBot.assertGoneByRelativeId(saveUi, resourceId, Timeouts.UI_TIMEOUT);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OnCreateServiceStatusVerifierActivity.java b/tests/autofillservice/src/android/autofillservice/cts/OnCreateServiceStatusVerifierActivity.java
deleted file mode 100644
index 69bd868..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OnCreateServiceStatusVerifierActivity.java
+++ /dev/null
@@ -1,61 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.getAutofillServiceName;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * Activity used to verify whether the service is enable or not when it's launched.
- */
-public class OnCreateServiceStatusVerifierActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "OnCreateServiceStatusVerifierActivity";
-
-    static final String SERVICE_NAME = android.autofillservice.cts.NoOpAutofillService.SERVICE_NAME;
-
-    private String mSettingsOnCreate;
-    private boolean mEnabledOnCreate;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.simple_save_activity);
-
-        mSettingsOnCreate = getAutofillServiceName();
-        mEnabledOnCreate = getAutofillManager().isEnabled();
-        Log.i(TAG, "On create: settings=" + mSettingsOnCreate + ", enabled=" + mEnabledOnCreate);
-    }
-
-    void assertServiceStatusOnCreate(boolean enabled) {
-        if (enabled) {
-            assertWithMessage("Wrong settings").that(mSettingsOnCreate)
-                .isEqualTo(SERVICE_NAME);
-            assertWithMessage("AutofillManager.isEnabled() is wrong").that(mEnabledOnCreate)
-                .isTrue();
-
-        } else {
-            assertWithMessage("Wrong settings").that(mSettingsOnCreate).isNull();
-            assertWithMessage("AutofillManager.isEnabled() is wrong").that(mEnabledOnCreate)
-                .isFalse();
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCancellationSignalListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeCancellationSignalListener.java
deleted file mode 100644
index 9ba3f6b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCancellationSignalListener.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.CancellationSignal;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link android.os.CancellationSignal.OnCancelListener} used to assert that
- * {@link android.os.CancellationSignal.OnCancelListener} was called, and just once.
- */
-public final class OneTimeCancellationSignalListener
-        implements CancellationSignal.OnCancelListener {
-    private final CountDownLatch mLatch = new CountDownLatch(1);
-    private final long mTimeoutMs;
-
-    public OneTimeCancellationSignalListener(long timeoutMs) {
-        mTimeoutMs = timeoutMs;
-    }
-
-    public void assertOnCancelCalled() throws Exception {
-        final boolean called = mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) waiting for onCancel()", mTimeoutMs)
-                .that(called).isTrue();
-    }
-
-    @Override
-    public void onCancel() {
-        mLatch.countDown();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java
deleted file mode 100644
index 4d7af94..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.widget.CompoundButton;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link android.widget.CompoundButton.OnCheckedChangeListener} used to assert a
- * {@link CompoundButton} was auto-filled properly.
- */
-final class OneTimeCompoundButtonListener implements CompoundButton.OnCheckedChangeListener {
-    private final String name;
-    private final CountDownLatch latch = new CountDownLatch(1);
-    private final CompoundButton button;
-    private final boolean expected;
-
-    OneTimeCompoundButtonListener(String name, CompoundButton button,
-            boolean expectedAutofillValue) {
-        this.name = name;
-        this.button = button;
-        this.expected = expectedAutofillValue;
-    }
-
-    @Override
-    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-        latch.countDown();
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on CompoundButton %s", FILL_TIMEOUT.ms(), name)
-            .that(set).isTrue();
-        final boolean actual = button.isChecked();
-        assertWithMessage("Wrong auto-fill value on CompoundButton %s", name)
-            .that(actual).isEqualTo(expected);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java
deleted file mode 100644
index 407861d..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.widget.DatePicker;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@OnDateChangedListener} used to assert a {@link DatePicker} was auto-filled properly.
- */
-final class OneTimeDateListener implements DatePicker.OnDateChangedListener {
-    private final String name;
-    private final CountDownLatch latch = new CountDownLatch(1);
-    private final DatePicker datePicker;
-    private final int expectedYear;
-    private final int expectedMonth;
-    private final int expectedDay;
-
-    OneTimeDateListener(String name, DatePicker datePicker, int expectedYear, int expectedMonth,
-            int expectedDay) {
-        this.name = name;
-        this.datePicker = datePicker;
-        this.expectedYear = expectedYear;
-        this.expectedMonth = expectedMonth;
-        this.expectedDay = expectedDay;
-    }
-
-    @Override
-    public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
-        latch.countDown();
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on DatePicker %s", FILL_TIMEOUT.ms(), name)
-            .that(set).isTrue();
-        assertWithMessage("Wrong year on DatePicker %s", name)
-            .that(datePicker.getYear()).isEqualTo(expectedYear);
-        assertWithMessage("Wrong month on DatePicker %s", name)
-            .that(datePicker.getMonth()).isEqualTo(expectedMonth);
-        assertWithMessage("Wrong day on DatePicker %s", name)
-            .that(datePicker.getDayOfMonth()).isEqualTo(expectedDay);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java
deleted file mode 100644
index 73ed648..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.widget.RadioGroup;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link android.widget.RadioGroup.OnCheckedChangeListener} used to assert an
- * {@link RadioGroup} was auto-filled properly.
- */
-final class OneTimeRadioGroupListener implements RadioGroup.OnCheckedChangeListener {
-    private final String name;
-    private final CountDownLatch latch = new CountDownLatch(1);
-    private final RadioGroup radioGroup;
-    private final int expected;
-
-    OneTimeRadioGroupListener(String name, RadioGroup radioGroup, int expectedAutoFilledValue) {
-        this.name = name;
-        this.radioGroup = radioGroup;
-        this.expected = expectedAutoFilledValue;
-    }
-
-    @Override
-    public void onCheckedChanged(RadioGroup group, int checkedId) {
-        latch.countDown();
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), name)
-            .that(set).isTrue();
-        final int actual = radioGroup.getCheckedRadioButtonId();
-        assertWithMessage("Wrong auto-fill value on RadioGroup %s", name)
-            .that(actual).isEqualTo(expected);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java
deleted file mode 100644
index 5fb5973..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.Spinner;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link OnItemSelectedListener} used to assert an {@link Spinner} was auto-filled properly.
- */
-final class OneTimeSpinnerListener implements OnItemSelectedListener {
-    private final String name;
-    private final CountDownLatch latch = new CountDownLatch(1);
-    private final Spinner spinner;
-    private final int expected;
-
-    OneTimeSpinnerListener(String name, Spinner spinner, int expectedAutoFilledValue) {
-        this.name = name;
-        this.spinner = spinner;
-        this.expected = expectedAutoFilledValue;
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on Spinner %s", FILL_TIMEOUT.ms(), name)
-            .that(set).isTrue();
-        final int actual = spinner.getSelectedItemPosition();
-        assertWithMessage("Wrong auto-fill value on Spinner %s", name)
-            .that(actual).isEqualTo(expected);
-    }
-
-    @Override
-    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-        latch.countDown();
-    }
-
-    @Override
-    public void onNothingSelected(AdapterView<?> parent) {
-        latch.countDown();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeTextWatcher.java
deleted file mode 100644
index db20c43..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeTextWatcher.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.text.TextWatcher;
-import android.widget.EditText;
-
-/**
- * Custom {@link TextWatcher} used to assert a {@link EditText} was auto-filled properly.
- */
-final class OneTimeTextWatcher extends MultipleTimesTextWatcher {
-
-    OneTimeTextWatcher(String name, EditText editText, CharSequence expectedAutofillValue) {
-        super(name, 1, editText, expectedAutofillValue);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivity.java
deleted file mode 100644
index 561b727..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivity.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.EditText;
-
-import androidx.annotation.Nullable;
-
-/**
- * Activity that has the following fields:
- *
- * <ul>
- *   <li>Address 1 EditText (id: address1)
- *   <li>Address 2 EditText (id: address2)
- *   <li>City EditText (id: city)
- *   <li>Favorite Color EditText (id: favorite_color)
- *   <li>Clear Button
- *   <li>SaveButton
- * </ul>
- *
- * <p>It's used to test auto-fill Save when not all fields are required.
- */
-public class OptionalSaveActivity extends AbstractAutoFillActivity {
-
-    static final String ID_ADDRESS1 = "address1";
-    static final String ID_ADDRESS2 = "address2";
-    static final String ID_CITY = "city";
-    static final String ID_FAVORITE_COLOR = "favorite_color";
-
-    EditText mAddress1;
-    EditText mAddress2;
-    EditText mCity;
-    EditText mFavoriteColor;
-    private Button mSaveButton;
-    private Button mClearButton;
-    private FillExpectation mExpectation;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.optional_save_activity);
-
-        mAddress1 = (EditText) findViewById(R.id.address1);
-        mAddress2 = (EditText) findViewById(R.id.address2);
-        mCity = (EditText) findViewById(R.id.city);
-        mFavoriteColor = (EditText) findViewById(R.id.favorite_color);
-        mSaveButton = (Button) findViewById(R.id.save);
-        mClearButton = (Button) findViewById(R.id.clear);
-        mSaveButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                save();
-            }
-        });
-        mClearButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                resetFields();
-            }
-        });
-    }
-
-    /**
-     * Resets the values of the input fields.
-     */
-    private void resetFields() {
-        mAddress1.setText("");
-        mAddress2.setText("");
-        mCity.setText("");
-        mFavoriteColor.setText("");
-    }
-
-    /**
-     * Emulates a save action.
-     */
-    void save() {
-        final Intent intent = new Intent(this, WelcomeActivity.class);
-        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Saved and sounded, please come again!");
-
-        startActivity(intent);
-        finish();
-    }
-
-    /**
-     * Sets the expectation for an auto-fill request, so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    void expectAutoFill(@Nullable String address1, @Nullable String address2, @Nullable String city,
-            @Nullable String favColor) {
-        mExpectation = new FillExpectation(address1, address2, city, favColor);
-        if (address1 != null) {
-            mAddress1.addTextChangedListener(mExpectation.address1Watcher);
-        }
-        if (address2 != null) {
-            mAddress2.addTextChangedListener(mExpectation.address2Watcher);
-        }
-        if (city != null) {
-            mCity.addTextChangedListener(mExpectation.cityWatcher);
-        }
-        if (favColor != null) {
-            mFavoriteColor.addTextChangedListener(mExpectation.favoriteColorWatcher);
-        }
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, String, String, String)}.
-     */
-    void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        if (mExpectation.address1Watcher != null) {
-            mExpectation.address1Watcher.assertAutoFilled();
-        }
-        if (mExpectation.address2Watcher != null) {
-            mExpectation.address2Watcher.assertAutoFilled();
-        }
-        if (mExpectation.cityWatcher != null) {
-            mExpectation.cityWatcher.assertAutoFilled();
-        }
-        if (mExpectation.favoriteColorWatcher != null) {
-            mExpectation.favoriteColorWatcher.assertAutoFilled();
-        }
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final OneTimeTextWatcher address1Watcher;
-        private final OneTimeTextWatcher address2Watcher;
-        private final OneTimeTextWatcher cityWatcher;
-        private final OneTimeTextWatcher favoriteColorWatcher;
-
-        private FillExpectation(@Nullable String address1, @Nullable String address2,
-                @Nullable String city, @Nullable String favColor) {
-            address1Watcher = address1 == null ? null
-                    : new OneTimeTextWatcher("address1", mAddress1, address1);
-            address2Watcher = address2 == null ? null
-                    : new OneTimeTextWatcher("address2", mAddress2, address2);
-            cityWatcher = city == null ? null : new OneTimeTextWatcher("city", mCity, city);
-            favoriteColorWatcher = favColor == null ? null
-                    : new OneTimeTextWatcher("favColor", mFavoriteColor, favColor);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
deleted file mode 100644
index fb4f49d..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
+++ /dev/null
@@ -1,768 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.OptionalSaveActivity.ID_ADDRESS1;
-import static android.autofillservice.cts.OptionalSaveActivity.ID_ADDRESS2;
-import static android.autofillservice.cts.OptionalSaveActivity.ID_CITY;
-import static android.autofillservice.cts.OptionalSaveActivity.ID_FAVORITE_COLOR;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.platform.test.annotations.AppModeFull;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.junit.Test;
-
-/**
- * Test case for an activity that contains 4 fields, but the service is only interested in 2-3 of
- * them for Save:
- *
- * <ul>
- *   <li>Address 1: required
- *   <li>Address 2: required
- *   <li>City: optional
- *   <li>Favorite Color: don't care - LOL
- * </ul>
- */
-@AppModeFull(reason = "Service-specific test")
-public class OptionalSaveActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<OptionalSaveActivity> {
-
-    private static final boolean EXPECT_NO_SAVE_UI = false;
-    private static final boolean EXPECT_SAVE_UI = true;
-
-    private OptionalSaveActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<OptionalSaveActivity> getActivityRule() {
-        return new AutofillActivityTestRule<OptionalSaveActivity>(OptionalSaveActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    /**
-     * Creates a standard builder common to all tests.
-     */
-    private CannedFillResponse.Builder newResponseBuilder() {
-        return new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_CITY)
-                .setOptionalSavableIds(ID_ADDRESS2);
-    }
-
-    @Test
-    public void testNoAutofillSaveAll() throws Exception {
-        noAutofillSaveOnChangeTest(() -> {
-            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
-            mActivity.mAddress2.setText("Simpsons House"); // not required
-            mActivity.mCity.setText("Springfield"); // required
-            mActivity.mFavoriteColor.setText("Yellow"); // lol
-        }, (s) -> {
-            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1), "742 Evergreen Terrace");
-            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
-            assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
-            assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Yellow");
-        });
-    }
-
-    @Test
-    public void testNoAutofillSaveRequiredOnly() throws Exception {
-        noAutofillSaveOnChangeTest(() -> {
-            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
-            mActivity.mCity.setText("Springfield"); // required
-        }, (s) -> {
-            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1), "742 Evergreen Terrace");
-            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "");
-            assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
-            assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "");
-        });
-    }
-
-    /**
-     * Tests the scenario where the service didn't have any data to autofill, and the user filled
-     * all fields, even the favorite color (LOL).
-     */
-    private void noAutofillSaveOnChangeTest(Runnable changes, Visitor<AssistStructure> assertions)
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(newResponseBuilder().build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started.
-        sReplier.getNextFillRequest();
-
-        // Manually fill fields...
-        mActivity.syncRunOnUiThread(changes);
-
-        // ...then tap save.
-        mActivity.save();
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
-
-        // Assert value of fields
-        assertions.visit(saveRequest.structure);
-    }
-
-    @Test
-    public void testNoAutofillFirstRequiredFieldMissing() throws Exception {
-        noAutofillNoChangeNoSaveTest(() -> {
-            // address1 is missing
-            mActivity.mAddress2.setText("Simpsons House"); // not required
-            mActivity.mCity.setText("Springfield"); // required
-            mActivity.mFavoriteColor.setText("Yellow"); // lol
-        });
-    }
-
-    @Test
-    public void testNoAutofillSecondRequiredFieldMissing() throws Exception {
-        noAutofillNoChangeNoSaveTest(() -> {
-            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
-            mActivity.mAddress2.setText("Simpsons House"); // not required
-            // city is missing
-            mActivity.mFavoriteColor.setText("Yellow"); // lol
-        });
-    }
-
-    /**
-     * Tests the scenario where the service didn't have any data to autofill, and the user filled
-     * didn't fill all required changes.
-     */
-    private void noAutofillNoChangeNoSaveTest(Runnable changes) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(newResponseBuilder().build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started.
-        sReplier.getNextFillRequest();
-
-        // Manually fill fields...
-        mActivity.syncRunOnUiThread(changes);
-
-        // ...then tap save.
-        mActivity.save();
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
-    }
-
-    @Test
-    public void testAutofillAllChangedAllSaveAll() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
-                // Initial dataset
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"),
-                // Changes
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
-                    mActivity.mAddress2.setText("Simpsons House"); // not required
-                    mActivity.mCity.setText("Springfield"); // required
-                    mActivity.mFavoriteColor.setText("Yellow"); // lol
-                }, (s) -> {
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
-                            "742 Evergreen Terrace");
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
-                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
-                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Yellow");
-                });
-    }
-
-    @Test
-    public void testAutofillAllChangedFirstRequiredSaveAll() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
-                // Initial dataset
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"),
-                // Changes
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
-                },
-                // Final state
-                (s) -> {
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
-                            "742 Evergreen Terrace");
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Shelbyville Bluffs");
-                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Shelbyville");
-                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
-                });
-    }
-
-    @Test
-    public void testAutofillAllChangedSecondRequiredSaveAll() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
-                // Initial dataset
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"),
-                // Changes
-                () -> {
-                    mActivity.mCity.setText("Springfield"); // required
-                },
-                // Final state
-                (s) -> {
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
-                            "Shelbyville Nuclear Power Plant");
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Shelbyville Bluffs");
-                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
-                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
-                });
-    }
-
-    @Test
-    public void testAutofillAllChangedOptionalSaveAll() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
-                // Initial dataset
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"),
-                // Changes
-                () -> {
-                    mActivity.mAddress2.setText("Simpsons House"); // not required
-                },
-                // Final state
-                (s) -> {
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
-                            "Shelbyville Nuclear Power Plant");
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
-                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Shelbyville");
-                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
-                });
-    }
-
-    /**
-     * Tests the scenario where the service autofilled the activity but the user changed fields
-     * that triggered Save.
-     */
-    private void autofillAndSaveOnChangeTest(CannedDataset.Builder dataset, Runnable changes,
-            Visitor<AssistStructure> assertions) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(newResponseBuilder()
-                .addDataset(dataset.setPresentation(createPresentation("Da Dataset")).build())
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> { mActivity.mAddress1.requestFocus(); });
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started.
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Da Dataset");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Manually fill fields...
-        mActivity.syncRunOnUiThread(changes);
-
-        // ...then tap save.
-        mActivity.save();
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
-
-        // Assert value of fields
-        assertions.visit(saveRequest.structure);
-    }
-
-    @Test
-    public void testAutofillAllChangedIgnored() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
-                    mActivity.mFavoriteColor.setText("Yellow"); // lol
-                });
-    }
-
-    @Test
-    public void testAutofillAllFirstRequiredChangedToEmpty() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
-                    mActivity.mAddress1.setText("");
-                });
-    }
-
-    @Test
-    public void testAutofillAllSecondRequiredChangedToNull() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
-                    mActivity.mCity.setText(null);
-                });
-    }
-
-    @Test
-    public void testAutofillAllFirstRequiredChangedBackToInitialState() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
-                    mActivity.mAddress1.setText("I'm different");
-                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
-                });
-    }
-
-    /**
-     * Tests the scenario where the service autofilled the activity and the user changed fields,
-     * but it did not triggered Save.
-     */
-    private void autofillNoChangeNoSaveTest(CannedDataset.Builder dataset, Runnable changes)
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(newResponseBuilder()
-                .addDataset(dataset.setPresentation(createPresentation("Da Dataset")).build())
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started.
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Da Dataset");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Manually fill fields...
-        mActivity.syncRunOnUiThread(changes);
-
-        // ...then tap save.
-        mActivity.save();
-
-        // Assert the snack bar is not shown.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_oneDatasetAllRequiredFields()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1, ID_ADDRESS2},
-                null,
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace");
-                    mActivity.mAddress2.setText("Simpsons House");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_oneDatasetRequiredAndOptionalFields()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace");
-                    mActivity.mAddress2.setText("Simpsons House");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_multipleDatasetsDataOnFirst()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace");
-                    mActivity.mAddress2.setText("Simpsons House");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build(),
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SV"))
-                    .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                    .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_multipleDatasetsDataOnSecond()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
-                    mActivity.mAddress2.setText("Shelbyville Bluffs");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build(),
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SV"))
-                    .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                    .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_requiredOnly()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_optionalOnly()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress2.setText("Simpsons House");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_optionalsOnlyNoRequired()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                null,
-                new String[] {ID_ADDRESS2, ID_CITY},
-                () -> {
-                    mActivity.mCity.setText("Springfield");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .setField(ID_CITY, "Springfield")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testShowSaveUiWhenUserManuallyFilledDifferentValue_requiredOnly()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
-                },
-                EXPECT_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testShowSaveUiWhenUserManuallyFilledDifferentValue_optionalOnly()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                null,
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress2.setText("Shelbyville Bluffs");
-                },
-                EXPECT_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    private void saveWhenUserFilledDatasetFields(@Nullable String[] requiredIds,
-            @Nullable String[] optionalIds, @NonNull Runnable changes, boolean expectSaveUi,
-            @NonNull CannedDataset...datasets) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, requiredIds);
-        if (optionalIds != null) {
-            response.setOptionalSavableIds(optionalIds);
-        }
-        for (CannedDataset dataset : datasets) {
-            response.addDataset(dataset);
-        }
-        sReplier.addResponse(response.build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Manually fill it.
-        mActivity.syncRunOnUiThread(changes);
-
-        // ...then tap save.
-        mActivity.save();
-
-        // Make sure the snack bar is shown as expected.
-        if (expectSaveUi) {
-            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_ADDRESS);
-        } else {
-            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
-        }
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserClearedAutofilledFieldThatIsRequired() throws Exception {
-        // Set service.
-        enableService();
-
-        mActivity.expectAutoFill("742 Evergreen Terrace", "Simpsons House",
-                "Springfield", "Yellow");
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
-                .setOptionalSavableIds(ID_CITY)
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("SF"))
-                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                        .setField(ID_ADDRESS2, "Simpsons House")
-                        .setField(ID_CITY, "Springfield")
-                        .setField(ID_FAVORITE_COLOR, "Yellow")
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mUiBot.selectDataset("SF");
-        mActivity.assertAutoFilled();
-
-        // Clear the field.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress2.setText(""));
-
-        // Trigger save...
-        mActivity.save();
-
-        // ...and make sure the snack bar is not shown.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
-    }
-
-    @Test
-    public void testShowSaveUiWhenUserClearedAutofilledFieldThatIsOptional() throws Exception {
-        // Set service.
-        enableService();
-
-        mActivity.expectAutoFill("742 Evergreen Terrace", "Simpsons House",
-                "Springfield", "Yellow");
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
-                .setOptionalSavableIds(ID_CITY)
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("SF"))
-                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                        .setField(ID_ADDRESS2, "Simpsons House")
-                        .setField(ID_CITY, "Springfield")
-                        .setField(ID_FAVORITE_COLOR, "Yellow")
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mUiBot.selectDataset("SF");
-        mActivity.assertAutoFilled();
-
-        // Clear the field.
-        mActivity.syncRunOnUiThread(() -> mActivity.mCity.setText(""));
-
-        // Trigger save...
-        mActivity.save();
-
-        // ...and make sure the snack bar is shown.
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
-
-        // Finally, assert values.
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS1),
-                "742 Evergreen Terrace");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS2),
-                "Simpsons House");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CITY), "");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_FAVORITE_COLOR),
-                "Yellow");
-    }
-
-    @Test
-    public void testShowUpdateWhenUserChangedOptionalValueFromDatasetAndRequiredNotFromDataset()
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Address 2 will be required but not available
-        mActivity.expectAutoFill("742 Evergreen Terrace", null, "Springfield", "Yellow");
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
-                .setOptionalSavableIds(ID_CITY)
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("SF"))
-                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                        .setField(ID_CITY, "Springfield")
-                        .setField(ID_FAVORITE_COLOR, "Yellow")
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mUiBot.selectDataset("SF");
-        mActivity.assertAutoFilled();
-
-        // Change required and optional field.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mAddress2.setText("Simpsons House");
-            mActivity.mCity.setText("Shelbyville");
-        });
-        // Trigger save...
-        mActivity.save();
-
-        // ...and make sure the snack bar is shown.
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
-
-        // Finally, assert values.
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS1),
-                "742 Evergreen Terrace");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS2),
-                "Simpsons House");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CITY), "Shelbyville");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_FAVORITE_COLOR),
-                "Yellow");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java
deleted file mode 100644
index ff955dd..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Simple activity showing R.layout.login_activity. Started outside of the test process.
- */
-public class OutOfProcessLoginActivity extends Activity {
-    private static final String TAG = "OutOfProcessLoginActivity";
-
-    private static OutOfProcessLoginActivity sInstance;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        Log.i(TAG, "onCreate(" + savedInstanceState + ")");
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.login_activity);
-
-        findViewById(R.id.login).setOnClickListener((v) -> finish());
-
-        sInstance = this;
-    }
-
-    @Override
-    protected void onStart() {
-        Log.i(TAG, "onStart()");
-        super.onStart();
-        try {
-            if (!getStartedMarker(this).createNewFile()) {
-                Log.e(TAG, "cannot write started file");
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "cannot write started file: " + e);
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        Log.i(TAG, "onStop()");
-        super.onStop();
-
-        try {
-            if (!getStoppedMarker(this).createNewFile()) {
-                Log.e(TAG, "could not write stopped marker");
-            } else {
-                Log.v(TAG, "wrote stopped marker");
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "could write stopped marker: " + e);
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        Log.i(TAG, "onDestroy()");
-        try {
-            if (!getDestroyedMarker(this).createNewFile()) {
-                Log.e(TAG, "could not write destroyed marker");
-            } else {
-                Log.v(TAG, "wrote destroyed marker");
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "could write destroyed marker: " + e);
-        }
-        super.onDestroy();
-        sInstance = null;
-    }
-
-    /**
-     * Get the file that signals that the activity has entered {@link Activity#onStop()}.
-     *
-     * @param context Context of the app
-     * @return The marker file that is written onStop()
-     */
-    @NonNull public static File getStoppedMarker(@NonNull Context context) {
-        return new File(context.getFilesDir(), "stopped");
-    }
-
-    /**
-     * Get the file that signals that the activity has entered {@link Activity#onStart()}.
-     *
-     * @param context Context of the app
-     * @return The marker file that is written onStart()
-     */
-    @NonNull public static File getStartedMarker(@NonNull Context context) {
-        return new File(context.getFilesDir(), "started");
-    }
-
-   /**
-     * Get the file that signals that the activity has entered {@link Activity#onDestroy()}.
-     *
-     * @param context Context of the app
-     * @return The marker file that is written onDestroy()
-     */
-    @NonNull public static File getDestroyedMarker(@NonNull Context context) {
-        return new File(context.getFilesDir(), "destroyed");
-    }
-
-    public static void finishIt() {
-        Log.v(TAG, "Finishing " + sInstance);
-        if (sInstance != null) {
-            sInstance.finish();
-        }
-    }
-
-    public static boolean hasInstance() {
-        return sInstance != null;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivityFinisherReceiver.java b/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivityFinisherReceiver.java
deleted file mode 100644
index b75785e..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivityFinisherReceiver.java
+++ /dev/null
@@ -1,35 +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.autofillservice.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-/**
- * A {@link BroadcastReceiver} that finishes {@link OutOfProcessLoginActivity}.
- */
-public class OutOfProcessLoginActivityFinisherReceiver extends BroadcastReceiver {
-
-    private static final String TAG = "OutOfProcessLoginActivityFinisherReceiver";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.i(TAG, "Goodbye, unfinished business!");
-        OutOfProcessLoginActivity.finishIt();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
deleted file mode 100644
index 9b17dd4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
+++ /dev/null
@@ -1,2281 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.GridActivity.ID_L1C1;
-import static android.autofillservice.cts.GridActivity.ID_L1C2;
-import static android.autofillservice.cts.GridActivity.ID_L2C1;
-import static android.autofillservice.cts.GridActivity.ID_L2C2;
-import static android.autofillservice.cts.GridActivity.ID_L3C1;
-import static android.autofillservice.cts.GridActivity.ID_L3C2;
-import static android.autofillservice.cts.GridActivity.ID_L4C1;
-import static android.autofillservice.cts.GridActivity.ID_L4C2;
-import static android.autofillservice.cts.Helper.UNUSED_AUTOFILL_VALUE;
-import static android.autofillservice.cts.Helper.assertHasFlags;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertValue;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.Helper.getMaxPartitions;
-import static android.autofillservice.cts.Helper.setMaxPartitions;
-import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.GridActivity.FillExpectation;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillResponse;
-
-import org.junit.Test;
-
-/**
- * Test case for an activity containing multiple partitions.
- */
-@AppModeFull(reason = "Service-specific test")
-public class PartitionedActivityTest extends AbstractGridActivityTestCase {
-
-    @Test
-    public void testAutofillTwoPartitionsSkipFirst() throws Exception {
-        // Set service.
-        enableService();
-
-        // Prepare 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-
-        // Trigger auto-fill on 1st partition.
-        focusCell(1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-        final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
-        final ViewNode p1l1c2 = assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
-        assertWithMessage("Focus on p1l1c1").that(p1l1c1.isFocused()).isTrue();
-        assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
-
-        // Make sure UI is shown, but don't tap it.
-        mUiBot.assertDatasets("l1c1");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("l1c2");
-
-        // Now tap a field in a different partition
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-
-        // Trigger auto-fill on 2nd partition.
-        focusCell(2, 1);
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.flags).isEqualTo(0);
-        final ViewNode p2l1c1 = assertTextIsSanitized(fillRequest2.structure, ID_L1C1);
-        final ViewNode p2l1c2 = assertTextIsSanitized(fillRequest2.structure, ID_L1C2);
-        final ViewNode p2l2c1 = assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
-        final ViewNode p2l2c2 = assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
-        assertWithMessage("Focus on p2l1c1").that(p2l1c1.isFocused()).isFalse();
-        assertWithMessage("Focus on p2l1c2").that(p2l1c2.isFocused()).isFalse();
-        assertWithMessage("Focus on p2l2c1").that(p2l2c1.isFocused()).isTrue();
-        assertWithMessage("Focus on p2l2c2").that(p2l2c2.isFocused()).isFalse();
-        // Make sure UI is shown, but don't tap it.
-        mUiBot.assertDatasets("l2c1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("l2c2");
-
-        // Now fill them
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-              .onCell(1, 1, "l1c1")
-              .onCell(1, 2, "l1c2");
-        focusCell(1, 1);
-        mUiBot.selectDataset("l1c1");
-        expectation1.assertAutoFilled();
-
-        // Change previous values to make sure they are not filled again
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.setText(1, 2, "L1C2");
-
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 1, "l2c1")
-                .onCell(2, 2, "l2c2");
-        focusCell(2, 2);
-        mUiBot.selectDataset("l2c2");
-        expectation2.assertAutoFilled();
-
-        // Make sure previous partition didn't change
-        assertThat(mActivity.getText(1, 1)).isEqualTo("L1C1");
-        assertThat(mActivity.getText(1, 2)).isEqualTo("L1C2");
-    }
-
-    @Test
-    public void testAutofillTwoPartitionsInSequence() throws Exception {
-        // Set service.
-        enableService();
-
-        // 1st partition
-        // Prepare.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 1"))
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger auto-fill.
-        focusCell(1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-
-        assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
-        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 1");
-
-        // Check the results.
-        expectation1.assertAutoFilled();
-
-        // 2nd partition
-        // Prepare.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 2"))
-                        .setField(ID_L2C1, "l2c1")
-                        .setField(ID_L2C2, "l2c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 1, "l2c1")
-                .onCell(2, 2, "l2c2");
-
-        // Trigger auto-fill.
-        focusCell(2, 1);
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.flags).isEqualTo(0);
-
-        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
-        assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
-        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 2");
-
-        // Check the results.
-        expectation2.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofill4PartitionsAutomatically() throws Exception {
-        autofill4PartitionsTest(false);
-    }
-
-    @Test
-    public void testAutofill4PartitionsManually() throws Exception {
-        autofill4PartitionsTest(true);
-    }
-
-    private void autofill4PartitionsTest(boolean manually) throws Exception {
-        // Set service.
-        enableService();
-
-        // 1st partition
-        // Prepare.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 1"))
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger auto-fill.
-        mActivity.triggerAutofill(manually, 1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-
-        if (manually) {
-            assertHasFlags(fillRequest1.flags, FLAG_MANUAL_REQUEST);
-            assertValue(fillRequest1.structure, ID_L1C1, "");
-        } else {
-            assertThat(fillRequest1.flags).isEqualTo(0);
-            assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
-        }
-        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 1");
-
-        // Check the results.
-        expectation1.assertAutoFilled();
-
-        // 2nd partition
-        // Prepare.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 2"))
-                        .setField(ID_L2C1, "l2c1")
-                        .setField(ID_L2C2, "l2c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 1, "l2c1")
-                .onCell(2, 2, "l2c2");
-
-        // Trigger auto-fill.
-        mActivity.triggerAutofill(manually, 2, 1);
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-
-        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
-        if (manually) {
-            assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
-            assertValue(fillRequest2.structure, ID_L2C1, "");
-        } else {
-            assertThat(fillRequest2.flags).isEqualTo(0);
-            assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
-        }
-        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 2");
-
-        // Check the results.
-        expectation2.assertAutoFilled();
-
-        // 3rd partition
-        // Prepare.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 3"))
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-
-        // Trigger auto-fill.
-        mActivity.triggerAutofill(manually, 3, 1);
-        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
-
-        assertValue(fillRequest3.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest3.structure, ID_L1C2, "l1c2");
-        assertValue(fillRequest3.structure, ID_L2C1, "l2c1");
-        assertValue(fillRequest3.structure, ID_L2C2, "l2c2");
-        if (manually) {
-            assertHasFlags(fillRequest3.flags, FLAG_MANUAL_REQUEST);
-            assertValue(fillRequest3.structure, ID_L3C1, "");
-        } else {
-            assertThat(fillRequest3.flags).isEqualTo(0);
-            assertTextIsSanitized(fillRequest3.structure, ID_L3C1);
-        }
-        assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 3");
-
-        // Check the results.
-        expectation3.assertAutoFilled();
-
-        // 4th partition
-        // Prepare.
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 4"))
-                        .setField(ID_L4C1, "l4c1")
-                        .setField(ID_L4C2, "l4c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "l4c1")
-                .onCell(4, 2, "l4c2");
-
-        // Trigger auto-fill.
-        mActivity.triggerAutofill(manually, 4, 1);
-        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
-
-        assertValue(fillRequest4.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest4.structure, ID_L1C2, "l1c2");
-        assertValue(fillRequest4.structure, ID_L2C1, "l2c1");
-        assertValue(fillRequest4.structure, ID_L2C2, "l2c2");
-        assertValue(fillRequest4.structure, ID_L3C1, "l3c1");
-        assertValue(fillRequest4.structure, ID_L3C2, "l3c2");
-        if (manually) {
-            assertHasFlags(fillRequest4.flags, FLAG_MANUAL_REQUEST);
-            assertValue(fillRequest4.structure, ID_L4C1, "");
-        } else {
-            assertThat(fillRequest4.flags).isEqualTo(0);
-            assertTextIsSanitized(fillRequest4.structure, ID_L4C1);
-        }
-        assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 4");
-
-        // Check the results.
-        expectation4.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofill4PartitionsMixManualAndAuto() throws Exception {
-        // Set service.
-        enableService();
-
-        // 1st partition - auto
-        // Prepare.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 1"))
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger auto-fill.
-        focusCell(1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-
-        assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
-        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 1");
-
-        // Check the results.
-        expectation1.assertAutoFilled();
-
-        // 2nd partition - manual
-        // Prepare
-        // Must set text before creating expectation, and it must be a subset of the dataset values,
-        // otherwise the UI won't be shown because of filtering
-        mActivity.setText(2, 1, "l2");
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 2"))
-                        .setField(ID_L2C1, "l2c1")
-                        .setField(ID_L2C2, "l2c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 1, "l2c1")
-                .onCell(2, 2, "l2c2");
-
-        // Trigger auto-fill.
-        mActivity.forceAutofill(2, 1);
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
-
-        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
-        assertValue(fillRequest2.structure, ID_L2C1, "l2");
-        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 2");
-
-        // Check the results.
-        expectation2.assertAutoFilled();
-
-        // 3rd partition - auto
-        // Prepare.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 3"))
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-
-        // Trigger auto-fill.
-        focusCell(3, 1);
-        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
-        assertThat(fillRequest3.flags).isEqualTo(0);
-
-        assertValue(fillRequest3.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest3.structure, ID_L1C2, "l1c2");
-        assertValue(fillRequest3.structure, ID_L2C1, "l2c1");
-        assertValue(fillRequest3.structure, ID_L2C2, "l2c2");
-        assertTextIsSanitized(fillRequest3.structure, ID_L3C1);
-        assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 3");
-
-        // Check the results.
-        expectation3.assertAutoFilled();
-
-        // 4th partition - manual
-        // Must set text before creating expectation, and it must be a subset of the dataset values,
-        // otherwise the UI won't be shown because of filtering
-        mActivity.setText(4, 1, "l4");
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 4"))
-                        .setField(ID_L4C1, "l4c1")
-                        .setField(ID_L4C2, "l4c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "l4c1")
-                .onCell(4, 2, "l4c2");
-
-        // Trigger auto-fill.
-        mActivity.forceAutofill(4, 1);
-        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest4.flags, FLAG_MANUAL_REQUEST);
-
-        assertValue(fillRequest4.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest4.structure, ID_L1C2, "l1c2");
-        assertValue(fillRequest4.structure, ID_L2C1, "l2c1");
-        assertValue(fillRequest4.structure, ID_L2C2, "l2c2");
-        assertValue(fillRequest4.structure, ID_L3C1, "l3c1");
-        assertValue(fillRequest4.structure, ID_L3C2, "l3c2");
-        assertValue(fillRequest4.structure, ID_L4C1, "l4");
-        assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 4");
-
-        // Check the results.
-        expectation4.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofillBundleDataIsPassedAlong() throws Exception {
-        // Set service.
-        enableService();
-
-        final Bundle extras = new Bundle();
-        extras.putString("numbers", "4");
-
-        // Prepare 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setExtras(extras)
-                .build();
-        sReplier.addResponse(response1);
-
-        // Trigger auto-fill on 1st partition.
-        focusCell(1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-        assertThat(fillRequest1.data).isNull();
-        mUiBot.assertDatasets("l1c1");
-
-        // Prepare 2nd partition; it replaces 'number' and adds 'numbers2'
-        extras.clear();
-        extras.putString("numbers", "48");
-        extras.putString("numbers2", "1516");
-
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setExtras(extras)
-                .build();
-        sReplier.addResponse(response2);
-
-        // Trigger auto-fill on 2nd partition
-        focusCell(2, 1);
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.flags).isEqualTo(0);
-        assertWithMessage("null bundle on request 2").that(fillRequest2.data).isNotNull();
-        assertWithMessage("wrong number of extras on request 2 bundle")
-                .that(fillRequest2.data.size()).isEqualTo(1);
-        assertThat(fillRequest2.data.getString("numbers")).isEqualTo("4");
-
-        // Prepare 3nd partition; it has no extras
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
-                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
-                        .build())
-                .setExtras(null)
-                .build();
-        sReplier.addResponse(response3);
-
-        // Trigger auto-fill on 3rd partition
-        focusCell(3, 1);
-        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
-        assertThat(fillRequest3.flags).isEqualTo(0);
-        assertWithMessage("null bundle on request 3").that(fillRequest2.data).isNotNull();
-        assertWithMessage("wrong number of extras on request 3 bundle")
-                .that(fillRequest3.data.size()).isEqualTo(2);
-        assertThat(fillRequest3.data.getString("numbers")).isEqualTo("48");
-        assertThat(fillRequest3.data.getString("numbers2")).isEqualTo("1516");
-
-
-        // Prepare 4th partition; it contains just 'numbers4'
-        extras.clear();
-        extras.putString("numbers4", "2342");
-
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L4C1, "l4c1", createPresentation("l4c1"))
-                        .setField(ID_L4C2, "l4c2", createPresentation("l4c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .setExtras(extras)
-                .build();
-        sReplier.addResponse(response4);
-
-        // Trigger auto-fill on 4th partition
-        focusCell(4, 1);
-        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
-        assertThat(fillRequest4.flags).isEqualTo(0);
-        assertWithMessage("non-null bundle on request 4").that(fillRequest4.data).isNull();
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        assertWithMessage("wrong number of extras on save request bundle")
-                .that(saveRequest.data.size()).isEqualTo(1);
-        assertThat(saveRequest.data.getString("numbers4")).isEqualTo("2342");
-    }
-
-    @Test
-    public void testSaveOneSaveInfoOnFirstPartitionWithIdsOnSecond() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L2C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        mActivity.setText(2, 1, "L2C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
-    }
-
-    @Test
-    public void testSaveOneSaveInfoOnSecondPartitionWithIdsOnFirst() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
-    }
-
-    @Test
-    public void testSaveTwoSaveInfosDifferentTypes() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
-                        ID_L2C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.setText(2, 1, "L2C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
-        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
-    }
-
-    @Test
-    public void testSaveThreeSaveInfosDifferentTypes() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
-                        ID_L2C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 3rd partition.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
-                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
-                        | SAVE_DATA_TYPE_USERNAME, ID_L3C1)
-                .build();
-        sReplier.addResponse(response3);
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.setText(2, 1, "L2C1");
-        mActivity.setText(3, 1, "L3C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD,
-                SAVE_DATA_TYPE_USERNAME);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
-        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
-        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
-    }
-
-    @Test
-    public void testSaveThreeSaveInfosDifferentTypesIncludingGeneric() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_GENERIC, ID_L2C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 3rd partition.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
-                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
-                        .build())
-                .setRequiredSavableIds(
-                        SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_GENERIC | SAVE_DATA_TYPE_USERNAME,
-                        ID_L3C1)
-                .build();
-        sReplier.addResponse(response3);
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.setText(2, 1, "L2C1");
-        mActivity.setText(3, 1, "L3C1");
-        mActivity.save();
-
-        // Make sure GENERIC type is not shown on snackbar
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_USERNAME);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
-        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
-        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
-    }
-
-    @Test
-    public void testSaveMoreThanThreeSaveInfosDifferentTypes() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
-                        ID_L2C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 3rd partition.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
-                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
-                        | SAVE_DATA_TYPE_USERNAME, ID_L3C1)
-                .build();
-        sReplier.addResponse(response3);
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 4th partition.
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L4C1, "l4c1", createPresentation("l4c1"))
-                        .setField(ID_L4C2, "l4c2", createPresentation("l4c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
-                        | SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_ADDRESS, ID_L4C1)
-                .build();
-        sReplier.addResponse(response4);
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.setText(2, 1, "L2C1");
-        mActivity.setText(3, 1, "L3C1");
-        mActivity.setText(4, 1, "L4C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
-        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
-        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
-        assertValue(saveRequest.structure, ID_L4C1, "L4C1");
-    }
-
-    @Test
-    public void testIgnoredFieldsDontTriggerAutofill() throws Exception {
-        // Set service.
-        enableService();
-
-        // Prepare 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setIgnoreFields(ID_L2C1, ID_L2C2)
-                .build();
-        sReplier.addResponse(response1);
-
-        // Trigger auto-fill on 1st partition.
-        focusCell(1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-        final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
-        final ViewNode p1l1c2 = assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
-        assertWithMessage("Focus on p1l1c1").that(p1l1c1.isFocused()).isTrue();
-        assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
-
-        // Make sure UI is shown on 1st partition
-        mUiBot.assertDatasets("l1c1");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("l1c2");
-
-        // Make sure UI is not shown on ignored partition
-        focusCell(2, 1);
-        mUiBot.assertNoDatasets();
-        focusCellNoWindowChange(2, 2);
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset, but they don't overlap, i.e.,
-     * each {@link FillResponse} only contain fields within the partition.
-     */
-    @Test
-    public void testAutofillMultipleDatasetsNoOverlap() throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P1D1"))
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P1D2"))
-                        .setField(ID_L1C1, "L1C1")
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L2C1, "l2c1")
-                        .setField(ID_L2C2, "l2c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, "L2C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 2, "L2C2");
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L3C1, "L3C1")
-                        .setField(ID_L3C2, "L3C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "L3C1")
-                .onCell(3, 2, "L3C2");
-
-        // Trigger partition.
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L4C1, "l4c1")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L4C1, "L4C1")
-                        .setField(ID_L4C2, "L4C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "l4c1");
-
-        // Trigger partition.
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        /*
-         *  Now move focus around to make sure the proper values are displayed each time.
-         */
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        focusCell(4, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        /*
-         *  Finally, autofill and check results.
-         */
-        focusCell(4, 1);
-        mUiBot.selectDataset("P4D1");
-        expectation4.assertAutoFilled();
-
-        focusCell(1, 1);
-        mUiBot.selectDataset("P1D1");
-        expectation1.assertAutoFilled();
-
-        focusCell(3, 1);
-        mUiBot.selectDataset("P3D2");
-        expectation3.assertAutoFilled();
-
-        focusCell(2, 2);
-        mUiBot.selectDataset("P2D2");
-        expectation2.assertAutoFilled();
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset, but they overlap, i.e.,
-     * some fields are present in more than one partition.
-     *
-     * <p>Whenever a new partition defines a field previously present in another partittion, that
-     * partition will "own" that field.
-     *
-     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
-     * the first.
-     */
-    @Test
-    public void testAutofillMultipleDatasetsOverlappingPicksFirst() throws Exception {
-        autofillMultipleDatasetsOverlapping(true);
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset, but they overlap, i.e.,
-     * some fields are present in more than one partition.
-     *
-     * <p>Whenever a new partition defines a field previously present in another partittion, that
-     * partition will "own" that field.
-     *
-     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
-     * the second.
-     */
-    @Test
-    public void testAutofillMultipleDatasetsOverlappingPicksSecond() throws Exception {
-        autofillMultipleDatasetsOverlapping(false);
-    }
-
-    private void autofillMultipleDatasetsOverlapping(boolean pickFirst) throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P1D1"))
-                        .setField(ID_L1C1, "1l1c1")
-                        .setField(ID_L1C2, "1l1c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P1D2"))
-                        .setField(ID_L1C1, "1L1C1")
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L1C1, "2l1c1") // from previous partition
-                        .setField(ID_L2C1, "2l2c1")
-                        .setField(ID_L2C2, "2l2c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, "2L2C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P2D1"); // changed
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L1C2, "3l1c2")
-                        .setField(ID_L3C1, "3l3c1")
-                        .setField(ID_L3C2, "3l3c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L2C2, "3l2c2")
-                        .setField(ID_L3C1, "3L3C1")
-                        .setField(ID_L3C2, "3L3C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-
-        // Trigger partition.
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P3D1"); // changed
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P3D2"); // changed
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L1C1, "4l1c1")
-                        .setField(ID_L1C2, "4l1c2")
-                        .setField(ID_L2C1, "4l2c1")
-                        .setField(ID_L2C2, "4l2c2")
-                        .setField(ID_L3C1, "4l3c1")
-                        .setField(ID_L3C2, "4l3c2")
-                        .setField(ID_L4C1, "4l4c1")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L1C1, "4L1C1")
-                        .setField(ID_L1C2, "4L1C2")
-                        .setField(ID_L2C1, "4L2C1")
-                        .setField(ID_L2C2, "4L2C2")
-                        .setField(ID_L3C1, "4L3C1")
-                        .setField(ID_L3C2, "4L3C2")
-                        .setField(ID_L1C1, "4L1C1")
-                        .setField(ID_L4C1, "4L4C1")
-                        .setField(ID_L4C2, "4L4C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-
-        // Trigger partition.
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        /*
-         * Finally, autofill and check results.
-         */
-        final FillExpectation expectation = mActivity.expectAutofill();
-        final String chosenOne;
-        if (pickFirst) {
-            expectation
-                .onCell(1, 1, "4l1c1")
-                .onCell(1, 2, "4l1c2")
-                .onCell(2, 1, "4l2c1")
-                .onCell(2, 2, "4l2c2")
-                .onCell(3, 1, "4l3c1")
-                .onCell(3, 2, "4l3c2")
-                .onCell(4, 1, "4l4c1");
-            chosenOne = "P4D1";
-        } else {
-            expectation
-                .onCell(1, 1, "4L1C1")
-                .onCell(1, 2, "4L1C2")
-                .onCell(2, 1, "4L2C1")
-                .onCell(2, 2, "4L2C2")
-                .onCell(3, 1, "4L3C1")
-                .onCell(3, 2, "4L3C2")
-                .onCell(4, 1, "4L4C1")
-                .onCell(4, 2, "4L4C2");
-            chosenOne = "P4D2";
-        }
-
-        focusCell(4, 1);
-        mUiBot.selectDataset(chosenOne);
-        expectation.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofillMultipleAuthDatasetsInSequence() throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final IntentSender auth11 = AuthenticationActivity.createSender(getContext(), 11,
-                new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build());
-        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth11)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth12)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Focus around different fields in the partition.
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        // Autofill it...
-        mUiBot.selectDataset("P1D1");
-        // ... and assert result
-        expectation1.assertAutoFilled();
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 21);
-        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
-                new CannedDataset.Builder()
-                    .setField(ID_L2C2, "L2C2")
-                    .build());
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth21)
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth22)
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 2, "L2C2");
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Focus around different fields in the partition.
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        // Autofill it...
-        mUiBot.selectDataset("P2D2");
-        // ... and assert result
-        expectation2.assertAutoFilled();
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
-                new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build());
-        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth31)
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth32)
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-
-        // Trigger partition.
-        focusCell(3, 2);
-        sReplier.getNextFillRequest();
-
-        // Focus around different fields in the partition.
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        // Autofill it...
-        mUiBot.selectDataset("P3D1");
-        // ... and assert result
-        expectation3.assertAutoFilled();
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
-        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
-                new CannedDataset.Builder()
-                    .setField(ID_L4C1, "L4C1")
-                    .setField(ID_L4C2, "L4C2")
-                    .build());
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth41)
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth42)
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "L4C1")
-                .onCell(4, 2, "L4C2");
-
-        // Trigger partition.
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        // Focus around different fields in the partition.
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        // Autofill it...
-        mUiBot.selectDataset("P4D2");
-        // ... and assert result
-        expectation4.assertAutoFilled();
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset and all datasets require auth,
-     * but they don't overlap, i.e., each {@link FillResponse} only contain fields within the
-     * partition.
-     */
-    @Test
-    public void testAutofillMultipleAuthDatasetsNoOverlap() throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final IntentSender auth11 = AuthenticationActivity.createSender(getContext(), 11,
-                new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build());
-        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth11)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth12)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 21);
-        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
-                new CannedDataset.Builder()
-                    .setField(ID_L2C2, "L2C2")
-                    .build());
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth21)
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth22)
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 2, "L2C2");
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
-                new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build());
-        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth31)
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth32)
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-
-        // Trigger partition.
-        focusCell(3, 2);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
-        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
-                new CannedDataset.Builder()
-                    .setField(ID_L4C1, "L4C1")
-                    .setField(ID_L4C2, "L4C2")
-                    .build());
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth41)
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth42)
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "L4C1")
-                .onCell(4, 2, "L4C2");
-
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        /*
-         *  Now move focus around to make sure the proper values are displayed each time.
-         */
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        focusCell(4, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        /*
-         *  Finally, autofill and check results.
-         */
-        focusCell(4, 1);
-        mUiBot.selectDataset("P4D2");
-        expectation4.assertAutoFilled();
-
-        focusCell(1, 1);
-        mUiBot.selectDataset("P1D1");
-        expectation1.assertAutoFilled();
-
-        focusCell(3, 1);
-        mUiBot.selectDataset("P3D1");
-        expectation3.assertAutoFilled();
-
-        focusCell(2, 2);
-        mUiBot.selectDataset("P2D2");
-        expectation2.assertAutoFilled();
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset and some datasets require auth,
-     * but they don't overlap, i.e., each {@link FillResponse} only contain fields within the
-     * partition.
-     */
-    @Test
-    public void testAutofillMultipleDatasetsMixedAuthNoAuthNoOverlap() throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .setPresentation(createPresentation("P1D1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth12)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
-                new CannedDataset.Builder()
-                    .setField(ID_L2C2, "L2C2")
-                    .build());
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L2C1, "l2c1")
-                        .setField(ID_L2C2, "l2c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth22)
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 2, "L2C2");
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
-                new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build());
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth31)
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L3C1, "L3C1")
-                        .setField(ID_L3C2, "L3C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-
-        // Trigger partition.
-        focusCell(3, 2);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth41)
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L4C1, "L4C1")
-                        .setField(ID_L4C2, "L4C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "L4C1")
-                .onCell(4, 2, "L4C2");
-
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        /*
-         *  Now move focus around to make sure the proper values are displayed each time.
-         */
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        focusCell(4, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        /*
-         *  Finally, autofill and check results.
-         */
-        focusCell(4, 1);
-        mUiBot.selectDataset("P4D2");
-        expectation4.assertAutoFilled();
-
-        focusCell(1, 1);
-        mUiBot.selectDataset("P1D1");
-        expectation1.assertAutoFilled();
-
-        focusCell(3, 1);
-        mUiBot.selectDataset("P3D1");
-        expectation3.assertAutoFilled();
-
-        focusCell(2, 2);
-        mUiBot.selectDataset("P2D2");
-        expectation2.assertAutoFilled();
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset - some authenticated and some
-     * not - but they overlap, i.e., some fields are present in more than one partition.
-     *
-     * <p>Whenever a new partition defines a field previously present in another partittion, that
-     * partition will "own" that field.
-     *
-     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
-     * the first.
-     */
-    @Test
-    public void testAutofillMultipleAuthDatasetsOverlapPickFirst() throws Exception {
-        autofillMultipleAuthDatasetsOverlapping(true);
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset - some authenticated and some
-     * not - but they overlap, i.e., some fields are present in more than one partition.
-     *
-     * <p>Whenever a new partition defines a field previously present in another partittion, that
-     * partition will "own" that field.
-     *
-     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
-     * the second.
-     */
-    @Test
-    public void testAutofillMultipleAuthDatasetsOverlapPickSecond() throws Exception {
-        autofillMultipleAuthDatasetsOverlapping(false);
-    }
-
-    private void autofillMultipleAuthDatasetsOverlapping(boolean pickFirst) throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "1l1c1")
-                        .setField(ID_L1C2, "1l1c2")
-                        .setPresentation(createPresentation("P1D1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth12)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 22,
-                new CannedDataset.Builder()
-                    .setField(ID_L1C1, "2l1c1") // from previous partition
-                    .setField(ID_L2C1, "2l2c1")
-                    .setField(ID_L2C2, "2l2c2")
-                    .build());
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth21)
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, "2L2C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P2D1"); // changed
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
-                new CannedDataset.Builder()
-                        .setField(ID_L1C2, "3l1c2") // from previous partition
-                        .setField(ID_L3C1, "3l3c1")
-                        .setField(ID_L3C2, "3l3c2")
-                        .build());
-        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth31)
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth32)
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-
-        // Trigger partition.
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P3D1"); // changed
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P3D2"); // changed
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41,
-                new CannedDataset.Builder()
-                        .setField(ID_L1C1, "4l1c1") // from previous partition
-                        .setField(ID_L1C2, "4l1c2") // from previous partition
-                        .setField(ID_L2C1, "4l2c1") // from previous partition
-                        .setField(ID_L2C2, "4l2c2") // from previous partition
-                        .setField(ID_L3C1, "4l3c1") // from previous partition
-                        .setField(ID_L3C2, "4l3c2") // from previous partition
-                        .setField(ID_L4C1, "4l4c1")
-                        .build());
-        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
-                new CannedDataset.Builder()
-                        .setField(ID_L1C1, "4L1C1") // from previous partition
-                        .setField(ID_L1C2, "4L1C2") // from previous partition
-                        .setField(ID_L2C1, "4L2C1") // from previous partition
-                        .setField(ID_L2C2, "4L2C2") // from previous partition
-                        .setField(ID_L3C1, "4L3C1") // from previous partition
-                        .setField(ID_L3C2, "4L3C2") // from previous partition
-                        .setField(ID_L1C1, "4L1C1") // from previous partition
-                        .setField(ID_L4C1, "4L4C1")
-                        .setField(ID_L4C2, "4L4C2")
-                        .build());
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth41)
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth42)
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-
-        // Trigger partition.
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        /*
-         * Finally, autofill and check results.
-         */
-        final FillExpectation expectation = mActivity.expectAutofill();
-        final String chosenOne;
-        if (pickFirst) {
-            expectation
-                .onCell(1, 1, "4l1c1")
-                .onCell(1, 2, "4l1c2")
-                .onCell(2, 1, "4l2c1")
-                .onCell(2, 2, "4l2c2")
-                .onCell(3, 1, "4l3c1")
-                .onCell(3, 2, "4l3c2")
-                .onCell(4, 1, "4l4c1");
-            chosenOne = "P4D1";
-        } else {
-            expectation
-                .onCell(1, 1, "4L1C1")
-                .onCell(1, 2, "4L1C2")
-                .onCell(2, 1, "4L2C1")
-                .onCell(2, 2, "4L2C2")
-                .onCell(3, 1, "4L3C1")
-                .onCell(3, 2, "4L3C2")
-                .onCell(4, 1, "4L4C1")
-                .onCell(4, 2, "4L4C2");
-            chosenOne = "P4D2";
-        }
-
-        focusCell(4, 1);
-        mUiBot.selectDataset(chosenOne);
-        expectation.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofillAllResponsesAuthenticated() throws Exception {
-        // Set service.
-        enableService();
-
-        // Prepare 1st partition.
-        final IntentSender auth1 = AuthenticationActivity.createSender(getContext(), 1,
-                new CannedFillResponse.Builder()
-                        .addDataset(new CannedDataset.Builder()
-                                .setPresentation(createPresentation("Partition 1"))
-                                .setField(ID_L1C1, "l1c1")
-                                .setField(ID_L1C2, "l1c2")
-                                .build())
-                        .build());
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .setPresentation(createPresentation("Auth 1"))
-                .setAuthentication(auth1, ID_L1C1, ID_L1C2)
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasets("Auth 1");
-
-        // Prepare 2nd partition.
-        final IntentSender auth2 = AuthenticationActivity.createSender(getContext(), 2,
-                new CannedFillResponse.Builder()
-                        .addDataset(new CannedDataset.Builder()
-                                .setPresentation(createPresentation("Partition 2"))
-                                .setField(ID_L2C1, "l2c1")
-                                .setField(ID_L2C2, "l2c2")
-                                .build())
-                        .build());
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .setPresentation(createPresentation("Auth 2"))
-                .setAuthentication(auth2, ID_L2C1, ID_L2C2)
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 1, "l2c1")
-                .onCell(2, 2, "l2c2");
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasets("Auth 2");
-
-        // Prepare 3rd partition.
-        final IntentSender auth3 = AuthenticationActivity.createSender(getContext(), 3,
-                new CannedFillResponse.Builder()
-                        .addDataset(new CannedDataset.Builder()
-                                .setPresentation(createPresentation("Partition 3"))
-                                .setField(ID_L3C1, "l3c1")
-                                .setField(ID_L3C2, "l3c2")
-                                .build())
-                        .build());
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .setPresentation(createPresentation("Auth 3"))
-                .setAuthentication(auth3, ID_L3C1, ID_L3C2)
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasets("Auth 3");
-
-        // Prepare 4th partition.
-        final IntentSender auth4 = AuthenticationActivity.createSender(getContext(), 4,
-                new CannedFillResponse.Builder()
-                        .addDataset(new CannedDataset.Builder()
-                                .setPresentation(createPresentation("Partition 4"))
-                                .setField(ID_L4C1, "l4c1")
-                                .setField(ID_L4C2, "l4c2")
-                                .build())
-                        .build());
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .setPresentation(createPresentation("Auth 4"))
-                .setAuthentication(auth4, ID_L4C1, ID_L4C2)
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "l4c1")
-                .onCell(4, 2, "l4c2");
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasets("Auth 4");
-
-        // Now play around the focus to make sure they still display the right values.
-
-        focusCell(1, 2);
-        mUiBot.assertDatasets("Auth 1");
-        focusCell(1, 1);
-        mUiBot.assertDatasets("Auth 1");
-
-        focusCell(3, 1);
-        mUiBot.assertDatasets("Auth 3");
-        focusCell(3, 2);
-        mUiBot.assertDatasets("Auth 3");
-
-        focusCell(2, 1);
-        mUiBot.assertDatasets("Auth 2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("Auth 4");
-
-        focusCell(2, 2);
-        mUiBot.assertDatasets("Auth 2");
-        focusCell(4, 1);
-        mUiBot.assertDatasets("Auth 4");
-
-        // Finally, autofill and check them.
-        focusCell(2, 1);
-        mUiBot.selectDataset("Auth 2");
-        mUiBot.selectDataset("Partition 2");
-        expectation2.assertAutoFilled();
-
-        focusCell(4, 1);
-        mUiBot.selectDataset("Auth 4");
-        mUiBot.selectDataset("Partition 4");
-        expectation4.assertAutoFilled();
-
-        focusCell(3, 1);
-        mUiBot.selectDataset("Auth 3");
-        mUiBot.selectDataset("Partition 3");
-        expectation3.assertAutoFilled();
-
-        focusCell(1, 1);
-        mUiBot.selectDataset("Auth 1");
-        mUiBot.selectDataset("Partition 1");
-        expectation1.assertAutoFilled();
-    }
-
-    @Test
-    public void testNoMorePartitionsAfterLimitReached() throws Exception {
-        final int maxBefore = getMaxPartitions();
-        try {
-            setMaxPartitions(1);
-            // Set service.
-            enableService();
-
-            // Prepare 1st partition.
-            final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                    .addDataset(new CannedDataset.Builder()
-                            .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                            .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                            .build())
-                    .build();
-            sReplier.addResponse(response1);
-
-            // Trigger autofill.
-            focusCell(1, 1);
-            sReplier.getNextFillRequest();
-
-            // Make sure UI is shown, but don't tap it.
-            mUiBot.assertDatasets("l1c1");
-            focusCell(1, 2);
-            mUiBot.assertDatasets("l1c2");
-
-            // Prepare 2nd partition.
-            final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                    .addDataset(new CannedDataset.Builder()
-                            .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                            .build())
-                    .build();
-            sReplier.addResponse(response2);
-
-            // Trigger autofill on 2nd partition.
-            focusCell(2, 1);
-
-            // Make sure it was ignored.
-            mUiBot.assertNoDatasets();
-
-            // Make sure 1st partition is still working.
-            focusCell(1, 2);
-            mUiBot.assertDatasets("l1c2");
-            focusCell(1, 1);
-            mUiBot.assertDatasets("l1c1");
-
-            // Prepare 3rd partition.
-            final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                    .addDataset(new CannedDataset.Builder()
-                            .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
-                            .build())
-                    .build();
-            sReplier.addResponse(response3);
-            // Trigger autofill on 3rd partition.
-            focusCell(3, 2);
-
-            // Make sure it was ignored.
-            mUiBot.assertNoDatasets();
-
-            // Make sure 1st partition is still working...
-            focusCell(1, 2);
-            mUiBot.assertDatasets("l1c2");
-            focusCell(1, 1);
-            mUiBot.assertDatasets("l1c1");
-
-            //...and can be autofilled.
-            final FillExpectation expectation = mActivity.expectAutofill()
-                    .onCell(1, 1, "l1c1");
-            mUiBot.selectDataset("l1c1");
-            expectation.assertAutoFilled();
-        } finally {
-            setMaxPartitions(maxBefore);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PasswordOnlyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/PasswordOnlyActivity.java
deleted file mode 100644
index 0a0d7a5..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PasswordOnlyActivity.java
+++ /dev/null
@@ -1,75 +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.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.autofill.AutofillId;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-public final class PasswordOnlyActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "PasswordOnlyActivity";
-
-    static final String EXTRA_USERNAME = "username";
-    static final String EXTRA_PASSWORD_AUTOFILL_ID = "password_autofill_id";
-
-    private TextView mWelcomeLabel;
-    private EditText mPasswordEditText;
-    private Button mLoginButton;
-    private String mUsername;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(getContentView());
-
-        mWelcomeLabel = findViewById(R.id.welcome);
-        mPasswordEditText = findViewById(R.id.password);
-        mLoginButton = findViewById(R.id.login);
-        mLoginButton.setOnClickListener((v) -> login());
-
-        mUsername = getIntent().getStringExtra(EXTRA_USERNAME);
-        final String welcomeMsg = "Welcome to the jungle, " + mUsername;
-        Log.v(TAG, welcomeMsg);
-        mWelcomeLabel.setText(welcomeMsg);
-        final AutofillId id = getIntent().getParcelableExtra(EXTRA_PASSWORD_AUTOFILL_ID);
-        if (id != null) {
-            Log.v(TAG, "Setting autofill id to " + id);
-            mPasswordEditText.setAutofillId(id);
-        }
-    }
-
-    protected int getContentView() {
-        return R.layout.password_only_activity;
-    }
-
-    public void focusOnPassword() {
-        syncRunOnUiThread(() -> mPasswordEditText.requestFocus());
-    }
-
-    void setPassword(String password) {
-        syncRunOnUiThread(() -> mPasswordEditText.setText(password));
-    }
-
-    void login() {
-        final String password = mPasswordEditText.getText().toString();
-        Log.i(TAG, "Login as " + mUsername + "/" + password);
-        finish();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivity.java
deleted file mode 100644
index 0c3a451..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-/**
- * Same as {@link LoginActivity}, but with {@code username} and {@code password} fields pre-filled.
- */
-public class PreFilledLoginActivity extends LoginActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.pre_filled_login_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
deleted file mode 100644
index 06191a5..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextFromResources;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertTextOnly;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.platform.test.annotations.AppModeFull;
-
-import org.junit.Test;
-
-/**
- * Covers scenarios where the behavior is different because some fields were pre-filled.
- */
-@AppModeFull(reason = "LoginActivityTest is enough")
-public class PreFilledLoginActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<PreFilledLoginActivity> {
-
-    private PreFilledLoginActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<PreFilledLoginActivity> getActivityRule() {
-        return new AutofillActivityTestRule<PreFilledLoginActivity>(PreFilledLoginActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testSanitization() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Change view contents.
-        mActivity.onUsernameLabel((v) -> v.setText("DA USER"));
-        mActivity.onPasswordLabel((v) -> v.setText(R.string.new_password_label));
-
-        // Trigger auto-fill.
-        mActivity.onUsername((v) -> v.requestFocus());
-
-        // Assert sanitization on fill request:
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // ...dynamic text should be sanitized.
-        assertTextIsSanitized(fillRequest.structure, ID_USERNAME_LABEL);
-
-        // ...password label should be ok because it was set from other resource id
-        assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
-                "new_password_label");
-
-        // ...username and password should be ok because they were set in the SML
-        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_USERNAME),
-                "secret_agent");
-        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_PASSWORD), "T0p S3cr3t");
-
-        // Trigger save
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        mActivity.tapLogin();
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Assert sanitization on save: everything should be available!
-        assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_USERNAME_LABEL), "DA USER");
-        assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
-                "new_password_label");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_USERNAME), "malkovich");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "malkovich");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java
deleted file mode 100644
index 0e14bc5..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-/**
- * A simple activity that upon submission launches {@link SimpleSaveActivity}.
- */
-public class PreSimpleSaveActivity extends AbstractAutoFillActivity {
-
-    static final String ID_PRE_LABEL = "preLabel";
-    static final String ID_PRE_INPUT = "preInput";
-
-    private static PreSimpleSaveActivity sInstance;
-
-    TextView mPreLabel;
-    EditText mPreInput;
-    Button mSubmit;
-
-    public static PreSimpleSaveActivity getInstance() {
-        return sInstance;
-    }
-
-    public PreSimpleSaveActivity() {
-        sInstance = this;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.pre_simple_save_activity);
-
-        mPreLabel = findViewById(R.id.preLabel);
-        mPreInput = findViewById(R.id.preInput);
-        mSubmit = findViewById(R.id.submit);
-
-        mSubmit.setOnClickListener((v) -> {
-            finish();
-            startActivity(new Intent(this, SimpleSaveActivity.class));
-        });
-    }
-
-    public FillExpectation expectAutoFill(String input) {
-        final FillExpectation expectation = new FillExpectation(input);
-        mPreInput.addTextChangedListener(expectation.mInputWatcher);
-        return expectation;
-    }
-
-    public EditText getPreInput() {
-        return mPreInput;
-    }
-
-    public final class FillExpectation {
-        private final OneTimeTextWatcher mInputWatcher;
-
-        private FillExpectation(String input) {
-            mInputWatcher = new OneTimeTextWatcher("input", mPreInput, input);
-        }
-
-        public void assertAutoFilled() throws Exception {
-            mInputWatcher.assertAutoFilled();
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
deleted file mode 100644
index 2b368bb..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
+++ /dev/null
@@ -1,390 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_STATIC_TEXT;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
-import static android.autofillservice.cts.PreSimpleSaveActivity.ID_PRE_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_LABEL;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.service.autofill.BatchUpdates;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.RegexValidator;
-import android.service.autofill.Validator;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import java.util.regex.Pattern;
-
-public class PreSimpleSaveActivityTest
-        extends CustomDescriptionWithLinkTestCase<PreSimpleSaveActivity> {
-
-    private static final AutofillActivityTestRule<PreSimpleSaveActivity> sActivityRule =
-            new AutofillActivityTestRule<PreSimpleSaveActivity>(PreSimpleSaveActivity.class, false);
-
-    public PreSimpleSaveActivityTest() {
-        super(PreSimpleSaveActivity.class);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<PreSimpleSaveActivity> getActivityRule() {
-        return sActivityRule;
-    }
-
-    @Override
-    protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception {
-        startActivity(false);
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Tap the link.
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // .. then do something to return to previous activity...
-        switch (type) {
-            case ROTATE_THEN_TAP_BACK_BUTTON:
-                mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
-                WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-                // not breaking on purpose
-            case TAP_BACK_BUTTON:
-                mUiBot.pressBack();
-                break;
-            case FINISH_ACTIVITY:
-                // ..then finishes it.
-                WelcomeActivity.finishIt();
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + type);
-        }
-
-        // ... and tap save.
-        final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-        mUiBot.saveForAutofill(newSaveUi, true);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT), "108");
-    }
-
-    @Override
-    protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
-            boolean manualRequest) throws Exception {
-        startActivity(false);
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Tap the link.
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Tap back to restore the Save UI...
-        mUiBot.pressBack();
-
-        // ...but don't tap it...
-        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...instead, do something to dismiss it:
-        switch (action) {
-            case TOUCH_OUTSIDE:
-                mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
-                break;
-            case TAP_NO_ON_SAVE_UI:
-                mUiBot.saveForAutofill(saveUi2, false);
-                break;
-            case TAP_YES_ON_SAVE_UI:
-                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-                assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT),
-                        "108");
-                break;
-            default:
-                throw new IllegalArgumentException("invalid action: " + action);
-        }
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Make sure previous session was finished.
-
-        // Now triggers a new session in the new activity (SaveActivity) and do business as usual...
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_EMAIL_ADDRESS, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        final SimpleSaveActivity newActivty = SimpleSaveActivity.getInstance();
-        if (manualRequest) {
-            newActivty.getAutofillManager().requestAutofill(newActivty.mInput);
-        } else {
-            newActivty.syncRunOnUiThread(() -> newActivty.mPassword.requestFocus());
-        }
-
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        newActivty.syncRunOnUiThread(() -> {
-            newActivty.mInput.setText("42");
-            newActivty.mCommit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
-    }
-
-    @Override
-    protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception {
-        startActivity(false);
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Tap the link.
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-        tapSaveUiLink(saveUi);
-
-        // Make sure linked activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        switch (type) {
-            case LAUNCH_PREVIOUS_ACTIVITY:
-                startActivityOnNewTask(PreSimpleSaveActivity.class);
-                mUiBot.assertShownByRelativeId(ID_INPUT);
-                break;
-            case LAUNCH_NEW_ACTIVITY:
-                // Launch a 3rd activity...
-                startActivityOnNewTask(LoginActivity.class);
-                mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
-                // ...then go back
-                mUiBot.pressBack();
-                mUiBot.assertShownByRelativeId(ID_INPUT);
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + type);
-        }
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Override
-    protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
-            throws Exception {
-        // Prepare activity.
-        startActivity(false);
-        mActivity.mPreInput.getRootView()
-                .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder.setCustomDescription(
-                        newCustomDescription(TrampolineWelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.getAutofillManager().requestAutofill(mActivity.mPreInput);
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-
-        // Save UI should be showing as well, since Trampoline finished.
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Go back and make sure it's showing the right activity.
-        // first BACK cancels save dialog
-        mUiBot.pressBack();
-        // second BACK cancel WelcomeActivity
-        mUiBot.pressBack();
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Now triggers a new session in the new activity (SaveActivity) and do business as usual...
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_EMAIL_ADDRESS, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        final SimpleSaveActivity newActivty = SimpleSaveActivity.getInstance();
-        newActivty.getAutofillManager().requestAutofill(newActivty.mInput);
-
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        newActivty.syncRunOnUiThread(() -> {
-            newActivty.mInput.setText("42");
-            newActivty.mCommit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
-
-        // ... and assert results
-        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest1.structure, ID_INPUT), "42");
-    }
-
-    @Override
-    protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
-        startActivity(false);
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final CustomDescription.Builder customDescription =
-                            newCustomDescriptionBuilder(WelcomeActivity.class);
-                    final RemoteViews update = newTemplate();
-                    if (updateLinkView) {
-                        update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
-                    } else {
-                        update.setCharSequence(R.id.static_text, "setText", "ME!");
-                    }
-                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_PRE_INPUT);
-                    final Validator validCondition = new RegexValidator(id, Pattern.compile(".*"));
-                    customDescription.batchUpdate(validCondition,
-                            new BatchUpdates.Builder().updateTemplate(update).build());
-                    builder.setCustomDescription(customDescription.build());
-                })
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Tap the link.
-        final UiObject2 saveUi;
-        if (updateLinkView) {
-            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD, "TAP ME IF YOU CAN");
-        } else {
-            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-            final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
-            assertThat(changed.getText()).isEqualTo("ME!");
-        }
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/RegexValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/RegexValidatorTest.java
deleted file mode 100644
index 7802c56..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/RegexValidatorTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.RegexValidator;
-import android.service.autofill.ValueFinder;
-import android.view.autofill.AutofillId;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.regex.Pattern;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class RegexValidatorTest {
-
-    @Test
-    public void allNullConstructor() {
-        assertThrows(NullPointerException.class, () -> new RegexValidator(null, null));
-    }
-
-    @Test
-    public void nullRegexConstructor() {
-        assertThrows(NullPointerException.class,
-                () -> new RegexValidator(new AutofillId(1), null));
-    }
-
-    @Test
-    public void nullAutofillIdConstructor() {
-        assertThrows(NullPointerException.class,
-                () -> new RegexValidator(null, Pattern.compile(".")));
-    }
-
-    @Test
-    public void unknownField() {
-        AutofillId unknownId = new AutofillId(42);
-
-        RegexValidator validator = new RegexValidator(unknownId, Pattern.compile(".*"));
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(unknownId)).thenReturn(null);
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-
-    @Test
-    public void singleFieldValid() {
-        AutofillId creditCardFieldId = new AutofillId(1);
-        RegexValidator validator = new RegexValidator(creditCardFieldId,
-                Pattern.compile("^\\s*\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?(\\d{4})\\s*$"));
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("1234 5678 9012 3456");
-        assertThat(validator.isValid(finder)).isTrue();
-
-        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("invalid");
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-
-    @Test
-    public void singleFieldInvalid() {
-        AutofillId id = new AutofillId(1);
-        RegexValidator validator = new RegexValidator(id, Pattern.compile("\\d*"));
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("123a456");
-
-        // Regex has to match the whole value
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java b/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
deleted file mode 100644
index 43ce97a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.service.autofill.SaveInfo.FLAG_DELAY_SAVE;
-import static android.service.autofill.SaveInfo.FLAG_DONT_SAVE_ON_FINISH;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.InternalSanitizer;
-import android.service.autofill.Sanitizer;
-import android.service.autofill.SaveInfo;
-import android.view.autofill.AutofillId;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class SaveInfoTest {
-
-    private final AutofillId mId = new AutofillId(42);
-    private final AutofillId[] mIdArray = { mId };
-    private final InternalSanitizer mSanitizer = mock(InternalSanitizer.class);
-
-    @Test
-    public void testRequiredIdsBuilder_null() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, null));
-    }
-
-    @Test
-    public void testRequiredIdsBuilder_empty() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, new AutofillId[] {}));
-    }
-
-    @Test
-    public void testRequiredIdsBuilder_nullEntry() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                        new AutofillId[] { null }));
-    }
-
-    @Test
-    public void testBuild_noOptionalIds() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC);
-        assertThrows(IllegalStateException.class, ()-> builder.build());
-    }
-
-    @Test
-    public void testSetOptionalIds_null() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                mIdArray);
-        assertThrows(IllegalArgumentException.class, ()-> builder.setOptionalIds(null));
-    }
-
-    @Test
-    public void testSetOptional_empty() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                mIdArray);
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.setOptionalIds(new AutofillId[] {}));
-    }
-
-    @Test
-    public void testSetOptional_nullEntry() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                mIdArray);
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.setOptionalIds(new AutofillId[] { null }));
-    }
-
-    @Test
-    public void testAddSanitizer_illegalArgs() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                mIdArray);
-        // Null sanitizer
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSanitizer(null, mId));
-        // Invalid sanitizer class
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSanitizer(mock(Sanitizer.class), mId));
-        // Null ids
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSanitizer(mSanitizer, (AutofillId[]) null));
-        // Empty ids
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSanitizer(mSanitizer, new AutofillId[] {}));
-        // Repeated ids
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSanitizer(mSanitizer, new AutofillId[] {mId, mId}));
-    }
-
-    @Test
-    public void testAddSanitizer_sameIdOnDifferentCalls() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                mIdArray);
-        builder.addSanitizer(mSanitizer, mId);
-        assertThrows(IllegalArgumentException.class, () -> builder.addSanitizer(mSanitizer, mId));
-    }
-
-    @Test
-    public void testBuild_invalid() {
-        // No nothing
-        assertThrows(IllegalStateException.class, () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC)
-                .build());
-        // Flag only, but invalid flag
-        assertThrows(IllegalStateException.class, () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC)
-                .setFlags(FLAG_DONT_SAVE_ON_FINISH).build());
-    }
-
-    @Test
-    public void testBuild_valid() {
-        // Required ids
-        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, mIdArray)
-                .build()).isNotNull();
-
-        // Optional ids
-        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC).setOptionalIds(mIdArray)
-                .build()).isNotNull();
-
-        // Delayed save
-        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC).setFlags(FLAG_DELAY_SAVE)
-                .build()).isNotNull();
-    }
-
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SecondActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SecondActivity.java
deleted file mode 100644
index 2aa60c9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SecondActivity.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.Bundle;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.widget.TextView;
-
-/**
- * Activity that is used to test restored mechanism will work while running below steps:
- * 1. Taps span on the save UI to start the ViewActionActivity.
- * 2. Launches the SecondActivity and immediately finish the ViewActionActivity.
- * 3. Presses back key on the SecondActivity.
- * The expected that the save UI should have been restored.
- */
-public class SecondActivity extends AbstractAutoFillActivity {
-
-    private static SecondActivity sInstance;
-
-    private static final String TAG = "SecondActivity";
-    static final String ID_WELCOME = "welcome";
-    static final String DEFAULT_MESSAGE = "Welcome second activity";
-
-    public SecondActivity() {
-        sInstance = this;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.welcome_activity);
-
-        TextView welcome = (TextView) findViewById(R.id.welcome);
-        welcome.setText(DEFAULT_MESSAGE);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        Log.v(TAG, "Setting sInstance to null onDestroy()");
-        sInstance = null;
-    }
-
-    static void finishIt() {
-        if (sInstance != null) {
-            sInstance.finish();
-        }
-    }
-
-    static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
-        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
-        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
-                .isEqualTo(DEFAULT_MESSAGE);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SelfDestructReceiver.java b/tests/autofillservice/src/android/autofillservice/cts/SelfDestructReceiver.java
deleted file mode 100644
index 8dc8dd9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SelfDestructReceiver.java
+++ /dev/null
@@ -1,36 +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.autofillservice.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Process;
-import android.util.Log;
-
-/**
- * A {@link BroadcastReceiver} that kills its process.
- */
-public class SelfDestructReceiver extends BroadcastReceiver {
-
-    private static final String TAG = "SelfDestructReceiver";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.i(TAG, "Goodbye, cruel world!");
-        Process.killProcess(Process.myPid());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ServiceDisabledForSureTest.java b/tests/autofillservice/src/android/autofillservice/cts/ServiceDisabledForSureTest.java
deleted file mode 100644
index 2841f78..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ServiceDisabledForSureTest.java
+++ /dev/null
@@ -1,80 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.disableAutofillService;
-import static android.autofillservice.cts.Helper.enableAutofillService;
-import static android.autofillservice.cts.OnCreateServiceStatusVerifierActivity.SERVICE_NAME;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/**
- * Test case that guarantee the service is disabled before the activity launches.
- */
-@AppModeFull(reason = "Service-specific test")
-public class ServiceDisabledForSureTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<OnCreateServiceStatusVerifierActivity> {
-
-    private static final String TAG = "ServiceDisabledForSureTest";
-
-    private OnCreateServiceStatusVerifierActivity mActivity;
-
-    @BeforeClass
-    public static void resetService() {
-        disableAutofillService(sContext);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity> getActivityRule() {
-        return new AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity>(
-                OnCreateServiceStatusVerifierActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Override
-    protected void prepareServicePreTest() {
-        // Doesn't need to prepare the service - that was already taken care of in a @BeforeClass -
-        // but to guarantee the test finishes in the proper state
-        Log.v(TAG, "prepareServicePreTest(): not doing anything");
-        mSafeCleanerRule.run(() ->assertThat(mActivity.getAutofillManager().isEnabled()).isFalse());
-    }
-
-    @Test
-    public void testIsAutofillEnabled() throws Exception {
-        mActivity.assertServiceStatusOnCreate(false);
-
-        final AutofillManager afm = mActivity.getAutofillManager();
-        Helper.assertAutofillEnabled(afm, false);
-
-        enableAutofillService(mContext, SERVICE_NAME);
-        Helper.assertAutofillEnabled(afm, true);
-
-        disableAutofillService(mContext);
-        Helper.assertAutofillEnabled(afm, false);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ServiceEnabledForSureTest.java b/tests/autofillservice/src/android/autofillservice/cts/ServiceEnabledForSureTest.java
deleted file mode 100644
index 60711d8..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ServiceEnabledForSureTest.java
+++ /dev/null
@@ -1,78 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.disableAutofillService;
-import static android.autofillservice.cts.Helper.enableAutofillService;
-import static android.autofillservice.cts.OnCreateServiceStatusVerifierActivity.SERVICE_NAME;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/**
- * Test case that guarantee the service is enabled before the activity launches.
- */
-public class ServiceEnabledForSureTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<OnCreateServiceStatusVerifierActivity> {
-
-    private static final String TAG = "ServiceEnabledForSureTest";
-
-    private OnCreateServiceStatusVerifierActivity mActivity;
-
-    @BeforeClass
-    public static void resetService() {
-        enableAutofillService(sContext, SERVICE_NAME);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity> getActivityRule() {
-        return new AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity>(
-                OnCreateServiceStatusVerifierActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Override
-    protected void prepareServicePreTest() {
-        // Doesn't need to prepare the service - that was already taken care of in a @BeforeClass -
-        // but to guarantee the test finishes in the proper state
-        Log.v(TAG, "prepareServicePreTest(): not doing anything");
-        mSafeCleanerRule.run(() ->assertThat(mActivity.getAutofillManager().isEnabled()).isTrue());
-    }
-
-    @Test
-    public void testIsAutofillEnabled() throws Exception {
-        mActivity.assertServiceStatusOnCreate(true);
-
-        final AutofillManager afm = mActivity.getAutofillManager();
-        Helper.assertAutofillEnabled(afm, true);
-
-        disableAutofillService(mContext);
-        Helper.assertAutofillEnabled(afm, false);
-
-        enableAutofillService(mContext, SERVICE_NAME);
-        Helper.assertAutofillEnabled(afm, true);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
index 8fb8fdf..774ba92 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
@@ -16,17 +16,17 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.ID_LOGIN;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.OutOfProcessLoginActivity.getDestroyedMarker;
-import static android.autofillservice.cts.OutOfProcessLoginActivity.getStartedMarker;
-import static android.autofillservice.cts.OutOfProcessLoginActivity.getStoppedMarker;
-import static android.autofillservice.cts.UiBot.LANDSCAPE;
-import static android.autofillservice.cts.UiBot.PORTRAIT;
+import static android.autofillservice.cts.activities.OutOfProcessLoginActivity.getDestroyedMarker;
+import static android.autofillservice.cts.activities.OutOfProcessLoginActivity.getStartedMarker;
+import static android.autofillservice.cts.activities.OutOfProcessLoginActivity.getStoppedMarker;
+import static android.autofillservice.cts.testcore.Helper.ID_LOGIN;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.UiBot.LANDSCAPE;
+import static android.autofillservice.cts.testcore.UiBot.PORTRAIT;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
 
@@ -41,6 +41,16 @@
 import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.app.assist.AssistStructure;
+import android.autofillservice.cts.activities.EmptyActivity;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.ManualAuthenticationActivity;
+import android.autofillservice.cts.activities.OutOfProcessLoginActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.autofillservice.cts.testcore.UiBot;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -108,8 +118,9 @@
 
     @After
     public void finishLoginActivityOnAnotherProcess() throws Exception {
-        runShellCommand("am broadcast --receiver-foreground "
-                + "-n android.autofillservice.cts/.OutOfProcessLoginActivityFinisherReceiver");
+        runShellCommand(
+                "am broadcast --receiver-foreground -n android.autofillservice.cts/.testcore"
+                        + ".OutOfProcessLoginActivityFinisherReceiver");
         mUiBot.assertGoneByRelativeId(ID_USERNAME, Timeouts.ACTIVITY_RESURRECTION);
 
         if (!OutOfProcessLoginActivity.hasInstance()) {
@@ -133,7 +144,7 @@
 
         // Kill activity that is in the background
         runShellCommand("am broadcast --receiver-foreground "
-                + "-n android.autofillservice.cts/.SelfDestructReceiver");
+                + "-n android.autofillservice.cts/.testcore.SelfDestructReceiver");
     }
 
     private void startAndWaitExternalActivity() throws Exception {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java b/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
deleted file mode 100644
index 54f391b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
+++ /dev/null
@@ -1,126 +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.autofillservice.cts;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
-import android.support.test.uiautomator.UiObject2;
-
-import com.android.compatibility.common.util.FeatureUtil;
-
-import org.junit.After;
-import org.junit.Test;
-
-@AppModeFull(reason = "Service-specific test")
-public class SettingsIntentTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<TrampolineForResultActivity> {
-
-    private static final int MY_REQUEST_CODE = 42;
-
-
-    protected TrampolineForResultActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<TrampolineForResultActivity> getActivityRule() {
-        return new AutofillActivityTestRule<TrampolineForResultActivity>(
-                TrampolineForResultActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @After
-    public void killSettings() {
-        // Make sure there's no Settings activity left, as it could fail future tests.
-        if (FeatureUtil.isAutomotive()) {
-            runShellCommand("am force-stop com.android.car.settings");
-        } else {
-            runShellCommand("am force-stop com.android.settings");
-        }
-    }
-
-    @Test
-    public void testMultipleServicesShown() throws Exception {
-        disableService();
-
-        // Launches Settings.
-        mActivity.startForResult(newSettingsIntent(), MY_REQUEST_CODE);
-
-        // Asserts services are shown.
-        mUiBot.assertShownByText(InstrumentedAutoFillService.sServiceLabel);
-        mUiBot.assertShownByText(InstrumentedAutoFillServiceCompatMode.sServiceLabel);
-        mUiBot.scrollToTextObject(NoOpAutofillService.SERVICE_LABEL);
-        mUiBot.assertShownByText(NoOpAutofillService.SERVICE_LABEL);
-        mUiBot.assertNotShowingForSure(BadAutofillService.SERVICE_LABEL);
-
-        // Finishes and asserts result.
-        mUiBot.pressBack();
-        mActivity.assertResult(Activity.RESULT_CANCELED);
-    }
-
-    @Test
-    public void testWarningShown_userRejectsByTappingBack() throws Exception {
-        disableService();
-
-        // Launches Settings.
-        mActivity.startForResult(newSettingsIntent(), MY_REQUEST_CODE);
-
-        // Asserts services are shown.
-        final UiObject2 object = mUiBot
-                .assertShownByText(InstrumentedAutoFillService.sServiceLabel);
-        object.click();
-
-        // TODO(b/79615759): should assert that "autofill_confirmation_message" is shown, but that
-        // string belongs to Settings - we need to move it to frameworks/base first (and/or use
-        // a resource id, also on framework).
-        // So, for now, just asserts the service name is showing again (in the popup), and the other
-        // services are not showing (because the popup hides then).
-
-        final UiObject2 msgObj = mUiBot.assertShownById("android:id/message");
-        final String msg = msgObj.getText();
-        assertWithMessage("Wrong warning message").that(msg)
-                .contains(InstrumentedAutoFillService.sServiceLabel);
-
-        // NOTE: assertion below is fine because it looks for the full text, not a substring
-        mUiBot.assertNotShowingForSure(InstrumentedAutoFillService.sServiceLabel);
-        mUiBot.assertNotShowingForSure(InstrumentedAutoFillServiceCompatMode.sServiceLabel);
-        mUiBot.assertNotShowingForSure(NoOpAutofillService.SERVICE_LABEL);
-        mUiBot.assertNotShowingForSure(BadAutofillService.SERVICE_LABEL);
-
-        // Finishes and asserts result.
-        mUiBot.pressBack();
-        mActivity.assertResult(Activity.RESULT_CANCELED);
-    }
-
-    // TODO(b/79615759): add testWarningShown_userRejectsByTappingCancel() and
-    // testWarningShown_userAccepts() - these tests would require adding the strings and resource
-    // ids to frameworks/base
-
-    private Intent newSettingsIntent() {
-        return new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .setData(Uri.parse("package:" + Helper.MY_PACKAGE));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java
deleted file mode 100644
index 8142f3a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * Activity that displays a "Finished login activity!" message after login.
- */
-public class SimpleAfterLoginActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "SimpleAfterLoginActivity";
-
-    static final String ID_AFTER_LOGIN = "after_login";
-
-    private static SimpleAfterLoginActivity sCurrentActivity;
-
-    public static SimpleAfterLoginActivity getCurrentActivity() {
-        return sCurrentActivity;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.simple_after_login_activity);
-
-        Log.v(TAG, "Set sCurrentActivity to this onCreate()");
-        sCurrentActivity = this;
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
-        sCurrentActivity = null;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java
deleted file mode 100644
index e14a6a4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * Activity that displays a "Launch login activity!" message before login.
- */
-public class SimpleBeforeLoginActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "SimpleBeforeLoginActivity";
-
-    static final String ID_BEFORE_LOGIN = "before_login";
-
-    private static SimpleBeforeLoginActivity sCurrentActivity;
-
-    public static SimpleBeforeLoginActivity getCurrentActivity() {
-        return sCurrentActivity;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.simple_before_login_activity);
-
-        Log.v(TAG, "Set sCurrentActivity to this onCreate()");
-        sCurrentActivity = this;
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
-        sCurrentActivity = null;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
deleted file mode 100644
index 2666269..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-/**
- * Simple activity that has an edit text and buttons to cancel or commit the autofill context.
- */
-public class SimpleSaveActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "SimpleSaveActivity";
-
-    public static final String ID_LABEL = "label";
-    public static final String ID_INPUT = "input";
-    public static final String ID_PASSWORD = "password";
-    public static final String ID_COMMIT = "commit";
-    public static final String TEXT_LABEL = "Label:";
-
-    private static SimpleSaveActivity sInstance;
-
-    TextView mLabel;
-    EditText mInput;
-    EditText mPassword;
-    Button mCancel;
-    Button mCommit;
-
-    private boolean mAutoCommit = true;
-    private boolean mClearFieldsOnSubmit = false;
-
-    public static SimpleSaveActivity getInstance() {
-        return sInstance;
-    }
-
-    public SimpleSaveActivity() {
-        sInstance = this;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.simple_save_activity);
-
-        mLabel = findViewById(R.id.label);
-        mInput = findViewById(R.id.input);
-        mPassword = findViewById(R.id.password);
-        mCancel = findViewById(R.id.cancel);
-        mCommit = findViewById(R.id.commit);
-
-        mCancel.setOnClickListener((v) -> getAutofillManager().cancel());
-        mCommit.setOnClickListener((v) -> onCommit());
-    }
-
-    private void onCommit() {
-        if (mClearFieldsOnSubmit) {
-            resetFields();
-        }
-        if (mAutoCommit) {
-            Log.d(TAG, "onCommit(): calling AFM.commit()");
-            getAutofillManager().commit();
-        } else {
-            Log.d(TAG, "onCommit(): NOT calling AFM.commit()");
-        }
-    }
-
-    private void resetFields() {
-        Log.d(TAG, "resetFields()");
-        mInput.setText("");
-        mPassword.setText("");
-    }
-
-    /**
-     * Defines whether the activity should automatically call {@link AutofillManager#commit()} when
-     * the commit button is tapped.
-     */
-    void setAutoCommit(boolean flag) {
-        mAutoCommit = flag;
-    }
-
-    /**
-     * Defines whether the activity should automatically clear its fields when submit is clicked.
-     */
-    void setClearFieldsOnSubmit(boolean flag) {
-        mClearFieldsOnSubmit = flag;
-    }
-
-    public FillExpectation expectAutoFill(String input) {
-        final FillExpectation expectation = new FillExpectation(input, null);
-        mInput.addTextChangedListener(expectation.mInputWatcher);
-        return expectation;
-    }
-
-    public FillExpectation expectAutoFill(String input, String password) {
-        final FillExpectation expectation = new FillExpectation(input, password);
-        mInput.addTextChangedListener(expectation.mInputWatcher);
-        mPassword.addTextChangedListener(expectation.mPasswordWatcher);
-        return expectation;
-    }
-
-    public EditText getInput() {
-        return mInput;
-    }
-
-    public final class FillExpectation {
-        private final OneTimeTextWatcher mInputWatcher;
-        private final OneTimeTextWatcher mPasswordWatcher;
-
-        private FillExpectation(String input, String password) {
-            mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
-            mPasswordWatcher = password == null
-                    ? null
-                    : new OneTimeTextWatcher("password", mPassword, password);
-        }
-
-        public void assertAutoFilled() throws Exception {
-            mInputWatcher.assertAutoFilled();
-            if (mPasswordWatcher != null) {
-                mPasswordWatcher.assertAutoFilled();
-            }
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
deleted file mode 100644
index c099043..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
+++ /dev/null
@@ -1,1874 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.AntiTrimmerTextWatcher.TRIMMER_PATTERN;
-import static android.autofillservice.cts.Helper.ID_STATIC_TEXT;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.LARGE_STRING;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextValue;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_COMMIT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_LABEL;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
-import static android.autofillservice.cts.SimpleSaveActivity.TEXT_LABEL;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.autofillservice.cts.SimpleSaveActivity.FillExpectation;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.BatchUpdates;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.RegexValidator;
-import android.service.autofill.SaveInfo;
-import android.service.autofill.TextValueSanitizer;
-import android.service.autofill.Validator;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.style.URLSpan;
-import android.view.View;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-
-import java.util.regex.Pattern;
-
-public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase<SimpleSaveActivity> {
-
-    private static final AutofillActivityTestRule<SimpleSaveActivity> sActivityRule =
-            new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class, false);
-
-    private static final AutofillActivityTestRule<WelcomeActivity> sWelcomeActivityRule =
-            new AutofillActivityTestRule<WelcomeActivity>(WelcomeActivity.class, false);
-
-    public SimpleSaveActivityTest() {
-        super(SimpleSaveActivity.class);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
-        return sActivityRule;
-    }
-
-    @Override
-    protected TestRule getMainTestRule() {
-        return RuleChain.outerRule(sActivityRule).around(sWelcomeActivityRule);
-    }
-
-    private void restartActivity() {
-        final Intent intent = new Intent(mContext.getApplicationContext(),
-                SimpleSaveActivity.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-        mActivity.startActivity(intent);
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndSave() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Select dataset.
-        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        mUiBot.selectDataset("YO");
-        autofillExpecation.assertAutoFilled();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("ID");
-            mActivity.mPassword.setText("PASS");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testAutoFillOneDatasetAndSave_largeAssistStructure() throws Exception {
-        startActivity();
-
-        mActivity.syncRunOnUiThread(
-                () -> mActivity.mInput.setAutofillHints(LARGE_STRING, LARGE_STRING, LARGE_STRING));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        final ViewNode inputOnFill = findNodeByResourceId(fillRequest.structure, ID_INPUT);
-        final String[] hintsOnFill = inputOnFill.getAutofillHints();
-        // Cannot compare these large strings directly becauise it could cause ANR
-        assertThat(hintsOnFill).hasLength(3);
-        Helper.assertEqualsToLargeString(hintsOnFill[0]);
-        Helper.assertEqualsToLargeString(hintsOnFill[1]);
-        Helper.assertEqualsToLargeString(hintsOnFill[2]);
-
-        // Select dataset.
-        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        mUiBot.selectDataset("YO");
-        autofillExpecation.assertAutoFilled();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("ID");
-            mActivity.mPassword.setText("PASS");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode inputOnSave = findNodeByResourceId(saveRequest.structure, ID_INPUT);
-        assertTextAndValue(inputOnSave, "ID");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
-
-        final String[] hintsOnSave = inputOnSave.getAutofillHints();
-        // Cannot compare these large strings directly becauise it could cause ANR
-        assertThat(hintsOnSave).hasLength(3);
-        Helper.assertEqualsToLargeString(hintsOnSave[0]);
-        Helper.assertEqualsToLargeString(hintsOnSave[1]);
-        Helper.assertEqualsToLargeString(hintsOnSave[2]);
-    }
-
-    /**
-     * Simple test that only uses UiAutomator to interact with the activity, so it indirectly
-     * tests the integration of Autofill with Accessibility.
-     */
-    @Test
-    public void testAutoFillOneDatasetAndSave_usingUiAutomatorOnly() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mUiBot.assertShownByRelativeId(ID_INPUT).click();
-        sReplier.getNextFillRequest();
-
-        // Select dataset...
-        mUiBot.selectDataset("YO");
-
-        // ...and assert autofilled values.
-        final UiObject2 input = mUiBot.assertShownByRelativeId(ID_INPUT);
-        final UiObject2 password = mUiBot.assertShownByRelativeId(ID_PASSWORD);
-
-        assertWithMessage("wrong value for 'input'").that(input.getText()).isEqualTo("id");
-        // TODO: password field is shown as **** ; ideally we should assert it's a password
-        // field, but UiAutomator does not exposes that info.
-        final String visiblePassword = password.getText();
-        assertWithMessage("'password' should not be visible").that(visiblePassword)
-            .isNotEqualTo("pass");
-        assertWithMessage("wrong value for 'password'").that(visiblePassword).hasLength(4);
-
-        // Trigger save...
-        input.setText("ID");
-        password.setText("PASS");
-        mUiBot.assertShownByRelativeId(ID_COMMIT).click();
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSave() throws Exception {
-        saveTest(false);
-    }
-
-    @Test
-    public void testSave_afterRotation() throws Exception {
-        assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
-        mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-        try {
-            saveTest(true);
-        } finally {
-            try {
-                mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-                cleanUpAfterScreenOrientationIsBackToPortrait();
-            } catch (Exception e) {
-                mSafeCleanerRule.add(e);
-            }
-        }
-    }
-
-    private void saveTest(boolean rotate) throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        if (rotate) {
-            // After the device rotates, the input field get focus and generate a new session.
-            sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-            mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
-            saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        }
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-    }
-
-    /**
-     * Emulates an app dyanmically adding the password field after username is typed.
-     */
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testPartitionedSave() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // 1st request
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Set 1st field but don't commit session
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
-        mUiBot.assertSaveNotShowing();
-
-        // 2nd request
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                        ID_INPUT, ID_PASSWORD)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPassword.setText("42");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(null, SAVE_DATA_TYPE_USERNAME,
-                SAVE_DATA_TYPE_PASSWORD);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertThat(saveRequest.contexts.size()).isEqualTo(2);
-
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
-    }
-
-    /**
-     * Emulates an app using fragments to display username and password in 2 steps.
-     */
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testDelayedSave() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // 1st fragment.
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE).build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger delayed save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        mUiBot.assertSaveNotShowing();
-
-        // 2nd fragment.
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                // Must explicitly set visitor, otherwise setRequiredSavableIds() would get the
-                // id from the 1st context
-                .setVisitor((contexts, builder) -> {
-                    final AutofillId passwordId =
-                            findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD);
-                    final AutofillId inputId =
-                            findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
-                    builder.setSaveInfo(new SaveInfo.Builder(
-                            SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                            new AutofillId[] {inputId, passwordId})
-                            .build());
-                })
-                .build());
-
-        // Trigger autofill on second "fragment"
-        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger delayed save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPassword.setText("42");
-            mActivity.mCommit.performClick();
-        });
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertThat(saveRequest.contexts.size()).isEqualTo(2);
-
-        // Get username from 1st request.
-        final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure();
-        assertTextAndValue(findNodeByResourceId(structure1, ID_INPUT), "108");
-
-        // Get password from 2nd request.
-        final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure();
-        assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "108");
-        assertTextAndValue(findNodeByResourceId(structure2, ID_PASSWORD), "42");
-    }
-
-    @Test
-    public void testSave_launchIntent() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.setOnSave(WelcomeActivity.createSender(mContext, "Saved by the bell"))
-                .addResponse(new CannedFillResponse.Builder()
-                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                        .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-
-            // Disable autofill so it's not triggered again after WelcomeActivity finishes
-            // and mActivity is resumed (with focus on mInput) after the session is closed
-            mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
-        });
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-        sReplier.getNextSaveRequest();
-
-        // ... and assert activity was launched
-        WelcomeActivity.assertShowing(mUiBot, "Saved by the bell");
-    }
-
-    @Test
-    public void testSaveThenStartNewSessionRightAwayShouldKeepSaveUi() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-
-        // Make sure Save UI for 1st session was shown....
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Start new Activity to have a new autofill session
-        startActivityOnNewTask(LoginActivity.class);
-
-        // Make sure LoginActivity started...
-        mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "id")
-                        .setField(ID_PASSWORD, "pwd")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-        // Trigger fill request on the LoginActivity
-        final LoginActivity act = LoginActivity.getCurrentActivity();
-        act.syncRunOnUiThread(() -> act.forceAutofillOnUsername());
-        sReplier.getNextFillRequest();
-
-        // Make sure Fill UI is not shown. And Save UI for 1st session was still shown.
-        mUiBot.assertNoDatasetsEver();
-        sReplier.assertNoUnhandledFillRequests();
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        mUiBot.waitForIdle();
-        // Trigger dismiss Save UI
-        mUiBot.pressBack();
-
-        // Make sure Save UI was not shown....
-        mUiBot.assertSaveNotShowing();
-        // Make sure Fill UI is shown.
-        mUiBot.assertDatasets("YO");
-    }
-
-    @Test
-    public void testCloseSaveUiThenStartNewSessionRightAway() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-
-        // Make sure Save UI for 1st session was shown....
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Trigger dismiss Save UI
-        mUiBot.pressBack();
-
-        // Make sure Save UI for 1st session was canceled.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // ...then start the new session right away (without finishing the activity).
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("");
-            mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
-        });
-        sReplier.getNextFillRequest();
-
-        // Make sure Fill UI is shown.
-        mUiBot.assertDatasets("YO");
-    }
-
-    @Test
-    public void testSaveWithParcelableOnClientState() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final AutofillId id = new AutofillId(42);
-        final Bundle clientState = new Bundle();
-        clientState.putParcelable("id", id);
-        clientState.putParcelable("my_id", new MyAutofillId(id));
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setExtras(clientState)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertMyClientState(saveRequest.data);
-
-        // Also check fillevent history
-        final FillEventHistory history = InstrumentedAutoFillService.getFillEventHistory(1);
-        @SuppressWarnings("deprecation")
-        final Bundle deprecatedState = history.getClientState();
-        assertMyClientState(deprecatedState);
-        assertMyClientState(history.getEvents().get(0).getClientState());
-    }
-
-    private void assertMyClientState(Bundle data) {
-        // Must set proper classpath before reading the data, otherwise Bundle will use it's
-        // on class classloader, which is the framework's.
-        data.setClassLoader(getClass().getClassLoader());
-
-        final AutofillId expectedId = new AutofillId(42);
-        final AutofillId actualId = data.getParcelable("id");
-        assertThat(actualId).isEqualTo(expectedId);
-        final MyAutofillId actualMyId = data.getParcelable("my_id");
-        assertThat(actualMyId).isEqualTo(new MyAutofillId(expectedId));
-    }
-
-    @Test
-    public void testCancelPreventsSaveUiFromShowing() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Cancel session.
-        mActivity.getAutofillManager().cancel();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    public void testDismissSave_byTappingBack() throws Exception {
-        startActivity();
-        dismissSaveTest(DismissType.BACK_BUTTON);
-    }
-
-    @Test
-    public void testDismissSave_byTappingHome() throws Exception {
-        startActivity();
-        dismissSaveTest(DismissType.HOME_BUTTON);
-    }
-
-    @Test
-    public void testDismissSave_byTouchingOutside() throws Exception {
-        startActivity();
-        dismissSaveTest(DismissType.TOUCH_OUTSIDE);
-    }
-
-    @Test
-    public void testDismissSave_byFocusingOutside() throws Exception {
-        startActivity();
-        dismissSaveTest(DismissType.FOCUS_OUTSIDE);
-    }
-
-    private void dismissSaveTest(DismissType dismissType) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Then make sure it goes away when user doesn't want it..
-        switch (dismissType) {
-            case BACK_BUTTON:
-                mUiBot.pressBack();
-                break;
-            case HOME_BUTTON:
-                mUiBot.pressHome();
-                break;
-            case TOUCH_OUTSIDE:
-                mUiBot.assertShownByText(TEXT_LABEL).click();
-                break;
-            case FOCUS_OUTSIDE:
-                mActivity.syncRunOnUiThread(() -> mActivity.mLabel.requestFocus());
-                mUiBot.assertShownByText(TEXT_LABEL).click();
-                break;
-            default:
-                throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
-        }
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    public void testTapHomeWhileDatasetPickerUiIsShowing() throws Exception {
-        startActivity();
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mUiBot.assertShownByRelativeId(ID_INPUT).click();
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("YO");
-        callback.assertUiShownEvent(mActivity.mInput);
-
-        // Go home, you are drunk!
-        mUiBot.pressHome();
-        mUiBot.assertNoDatasets();
-        callback.assertUiHiddenEvent(mActivity.mInput);
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO2"))
-                        .build())
-                .build());
-
-        // Switch back to the activity.
-        restartActivity();
-        mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
-        sReplier.getNextFillRequest();
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("YO2");
-        callback.assertUiShownEvent(mActivity.mInput);
-
-        // Now autofill it.
-        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        mUiBot.selectDataset(datasetPicker, "YO2");
-        autofillExpecation.assertAutoFilled();
-    }
-
-    @Test
-    public void testTapHomeWhileSaveUiIsShowing() throws Exception {
-        startActivity();
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save, but don't tap it.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Go home, you are drunk!
-        mUiBot.pressHome();
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Prepare the response for the next session, which will be automatically triggered
-        // when the activity is brought back.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Switch back to the activity.
-        restartActivity();
-        mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger and select UI.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
-        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        mUiBot.selectDataset("YO");
-
-        // Assert it.
-        autofillExpecation.assertAutoFilled();
-    }
-
-    @Override
-    protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception {
-        startActivity();
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // .. then do something to return to previous activity...
-        switch (type) {
-            case ROTATE_THEN_TAP_BACK_BUTTON:
-                // After the device rotates, the input field get focus and generate a new session.
-                sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-                mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
-                WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-                // not breaking on purpose
-            case TAP_BACK_BUTTON:
-                // ..then go back and save it.
-                mUiBot.pressBack();
-                break;
-            case FINISH_ACTIVITY:
-                // ..then finishes it.
-                WelcomeActivity.finishIt();
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + type);
-        }
-        // Make sure previous activity is back...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // ... and tap save.
-        final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-        mUiBot.saveForAutofill(newSaveUi, true);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-    }
-
-    @Override
-    protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
-        sReplier.getNextFillRequest();
-    }
-
-    @Override
-    protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
-            boolean manualRequest) throws Exception {
-        startActivity();
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown.
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Tap back to restore the Save UI...
-        mUiBot.pressBack();
-        // Make sure previous activity is back...
-        mUiBot.assertShownByRelativeId(ID_LABEL);
-
-        // ...but don't tap it...
-        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // ...instead, do something to dismiss it:
-        switch (action) {
-            case TOUCH_OUTSIDE:
-                mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
-                break;
-            case TAP_NO_ON_SAVE_UI:
-                mUiBot.saveForAutofill(saveUi2, false);
-                break;
-            case TAP_YES_ON_SAVE_UI:
-                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-                assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-                break;
-            default:
-                throw new IllegalArgumentException("invalid action: " + action);
-        }
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Now triggers a new session and do business as usual...
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        if (manualRequest) {
-            mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
-        } else {
-            mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
-        }
-
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("42");
-            mActivity.mCommit.performClick();
-        });
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
-    }
-
-    @Override
-    protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception {
-        startActivity(false);
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        switch (type) {
-            case LAUNCH_PREVIOUS_ACTIVITY:
-                startActivityOnNewTask(SimpleSaveActivity.class);
-                break;
-            case LAUNCH_NEW_ACTIVITY:
-                // Launch a 3rd activity...
-                startActivityOnNewTask(LoginActivity.class);
-                mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
-                // ...then go back
-                mUiBot.pressBack();
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + type);
-        }
-        // Make sure right activity is showing
-        mUiBot.assertShownByRelativeId(ID_INPUT, Timeouts.ACTIVITY_RESURRECTION);
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testSelectedDatasetsAreSentOnSaveRequest() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                // Added on reversed order on purpose
-                .addDataset(new CannedDataset.Builder()
-                        .setId("D2")
-                        .setField(ID_INPUT, "id again")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("D2"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("D1")
-                        .setField(ID_INPUT, "id")
-                        .setPresentation(createPresentation("D1"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Select 1st dataset.
-        final FillExpectation autofillExpecation1 = mActivity.expectAutoFill("id");
-        final UiObject2 picker1 = mUiBot.assertDatasets("D2", "D1");
-        mUiBot.selectDataset(picker1, "D1");
-        autofillExpecation1.assertAutoFilled();
-
-        // Select 2nd dataset.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
-        final FillExpectation autofillExpecation2 = mActivity.expectAutoFill("id again", "pass");
-        final UiObject2 picker2 = mUiBot.assertDatasets("D2");
-        mUiBot.selectDataset(picker2, "D2");
-        autofillExpecation2.assertAutoFilled();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("ID");
-            mActivity.mPassword.setText("PASS");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
-        assertThat(saveRequest.datasetIds).containsExactly("D1", "D2").inOrder();
-    }
-
-    @Override
-    protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
-            throws Exception {
-        // Prepare activity.
-        startActivity();
-        mActivity.mInput.getRootView()
-                .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(
-                                newCustomDescription(TrampolineWelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-
-        // Save UI should be showing as well, since Trampoline finished.
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Dismiss Save Dialog
-        mUiBot.pressBack();
-        // Go back and make sure it's showing the right activity.
-        mUiBot.pressBack();
-        mUiBot.assertShownByRelativeId(ID_LABEL);
-
-        // Now start a new session.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD)
-                .build());
-        mActivity.getAutofillManager().requestAutofill(mActivity.mPassword);
-        sReplier.getNextFillRequest();
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPassword.setText("42");
-            mActivity.mCommit.performClick();
-        });
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
-    }
-
-    @Test
-    public void testSanitizeOnSaveWhenAppChangeValues() throws Exception {
-        startActivity();
-
-        // Set listeners that will change the saved value
-        new AntiTrimmerTextWatcher(mActivity.mInput);
-        new AntiTrimmerTextWatcher(mActivity.mPassword);
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
-                            passwordId);
-                })
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("pass");
-            mActivity.mCommit.performClick();
-        });
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
-        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testSanitizeOnSaveNoChange() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setOptionalSavableIds(ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
-                            passwordId);
-                })
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("#id#");
-            mActivity.mPassword.setText("#pass#");
-            mActivity.mCommit.performClick();
-        });
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
-        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testDontSaveWhenSanitizedValueForRequiredFieldDidntChange() throws Exception {
-        startActivity();
-
-        // Set listeners that will change the saved value
-        new AntiTrimmerTextWatcher(mActivity.mInput);
-        new AntiTrimmerTextWatcher(mActivity.mPassword);
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
-                            passwordId);
-                })
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("pass");
-            mActivity.mCommit.performClick();
-        });
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testDontSaveWhenSanitizedValueForOptionalFieldDidntChange() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setOptionalSavableIds(ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("(pass) "), "$1"),
-                            passwordId);
-                })
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("#pass#");
-            mActivity.mCommit.performClick();
-        });
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testDontSaveWhenRequiredFieldFailedSanitization() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
-                            inputId, passwordId);
-                })
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "#id#")
-                        .setField(ID_PASSWORD, "#pass#")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("pass");
-            mActivity.mCommit.performClick();
-        });
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testDontSaveWhenOptionalFieldFailedSanitization() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setOptionalSavableIds(ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
-                            inputId, passwordId);
-
-                })
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "#pass#")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("pass");
-            mActivity.mCommit.performClick();
-        });
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testDontSaveWhenInitialValueAndNoUserInputAndServiceDatasets() throws Throwable {
-        // Prepare activitiy.
-        startActivity();
-        mActivity.syncRunOnUiThread(() -> {
-            // NOTE: input's value must be a subset of the dataset value, otherwise the dataset
-            // picker is filtered out
-            mActivity.mInput.setText("f");
-            mActivity.mPassword.setText("b");
-        });
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "foo")
-                        .setField(ID_PASSWORD, "bar")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-
-        // Trigger save.
-        mActivity.getAutofillManager().commit();
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    enum SetTextCondition {
-        NORMAL,
-        HAS_SESSION,
-        EMPTY_TEXT,
-        FOCUSED,
-        NOT_IMPORTANT_FOR_AUTOFILL,
-        INVISIBLE
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should trigger autofill and
-     * show Save UI.
-     */
-    @Test
-    public void testShowSaveUiWhenSetTextAutomatically() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NORMAL);
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
-     * when there is an existing session.
-     */
-    @Test
-    public void testNotTriggerAutofillWhenSetTextWhileSessionExists() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.HAS_SESSION);
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
-     * when the text is empty.
-     */
-    @Test
-    public void testNotTriggerAutofillWhenSetTextWhileEmptyText() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.EMPTY_TEXT);
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
-     * when the field is focused.
-     */
-    @Test
-    public void testNotTriggerAutofillWhenSetTextWhileFocused() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.FOCUSED);
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
-     * when the field is not important for autofill.
-     */
-    @Test
-    public void testNotTriggerAutofillWhenSetTextWhileNotImportantForAutofill() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NOT_IMPORTANT_FOR_AUTOFILL);
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
-     * when the field is not visible.
-     */
-    @Test
-    public void testNotTriggerAutofillWhenSetTextWhileInvisible() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.INVISIBLE);
-    }
-
-    private void triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition condition)
-            throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        CharSequence inputText = "108";
-
-        switch (condition) {
-            case NORMAL:
-                // Nothing.
-                break;
-            case HAS_SESSION:
-                mActivity.syncRunOnUiThread(() -> {
-                    mActivity.mInput.setText("100");
-                });
-                sReplier.getNextFillRequest();
-                break;
-            case EMPTY_TEXT:
-                inputText = "";
-                break;
-            case FOCUSED:
-                mActivity.syncRunOnUiThread(() -> {
-                    mActivity.mInput.requestFocus();
-                });
-                sReplier.getNextFillRequest();
-                break;
-            case NOT_IMPORTANT_FOR_AUTOFILL:
-                mActivity.syncRunOnUiThread(() -> {
-                    mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
-                });
-                break;
-            case INVISIBLE:
-                mActivity.syncRunOnUiThread(() -> {
-                    mActivity.mInput.setVisibility(View.INVISIBLE);
-                });
-                break;
-            default:
-                throw new IllegalArgumentException("invalid condition: " + condition);
-        }
-
-        // Trigger autofill by setting text.
-        final CharSequence text = inputText;
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText(text);
-        });
-
-        if (condition == SetTextCondition.NORMAL) {
-            sReplier.getNextFillRequest();
-
-            mActivity.syncRunOnUiThread(() -> {
-                mActivity.mInput.setText("100");
-                mActivity.mCommit.performClick();
-            });
-
-            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        } else {
-            sReplier.assertOnFillRequestNotCalled();
-        }
-    }
-
-    @Test
-    public void testExplicitlySaveButton() throws Exception {
-        explicitlySaveButtonTest(false, 0);
-    }
-
-    @Test
-    public void testExplicitlySaveButtonWhenAppClearFields() throws Exception {
-        explicitlySaveButtonTest(true, 0);
-    }
-
-    @Test
-    public void testExplicitlySaveButtonOnly() throws Exception {
-        explicitlySaveButtonTest(false, SaveInfo.FLAG_DONT_SAVE_ON_FINISH);
-    }
-
-    /**
-     * Tests scenario where service explicitly indicates which button is used to save.
-     */
-    private void explicitlySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception {
-        final boolean testBitmap = false;
-        startActivity();
-        mActivity.setAutoCommit(false);
-        mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit);
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveTriggerId(mActivity.mCommit.getAutofillId())
-                .setSaveInfoFlags(flags)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
-
-        // Take a screenshot to make sure button doesn't disappear.
-        final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
-        assertThat(commitBefore.toUpperCase()).isEqualTo("COMMIT");
-        // Disable unnecessary screenshot tests as takeScreenshot() fails on some device.
-
-        final Bitmap screenshotBefore = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
-                : null;
-
-        // Save it...
-        mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // Make sure save button is showning (it was removed on earlier versions of the feature)
-        final String commitAfter = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
-        assertThat(commitAfter.toUpperCase()).isEqualTo("COMMIT");
-        final Bitmap screenshotAfter = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
-                : null;
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-
-        if (testBitmap) {
-            Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter);
-        }
-    }
-
-    @Override
-    protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
-        startActivity();
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    // Set response with custom description
-                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
-                    final CustomDescription.Builder customDescription =
-                            newCustomDescriptionBuilder(WelcomeActivity.class);
-                    final RemoteViews update = newTemplate();
-                    if (updateLinkView) {
-                        update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
-                    } else {
-                        update.setCharSequence(R.id.static_text, "setText", "ME!");
-                    }
-                    Validator validCondition = new RegexValidator(id, Pattern.compile(".*"));
-                    customDescription.batchUpdate(validCondition,
-                            new BatchUpdates.Builder().updateTemplate(update).build());
-
-                    builder.setCustomDescription(customDescription.build());
-                })
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi;
-        if (updateLinkView) {
-            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC, "TAP ME IF YOU CAN");
-        } else {
-            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-            final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
-            assertThat(changed.getText()).isEqualTo("ME!");
-        }
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    enum DescriptionType {
-        SUCCINCT,
-        CUSTOM,
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the custom description, then the new activity
-     * finishes:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnCustomDescription_thenTapBack() throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
-                ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the succinct description, then the new activity
-     * finishes:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnSuccinctDescription_thenTapBack() throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
-                ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the custom description, then the new activity
-     * starts an another activity then it finishes:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnCustomDescription_forwardAnotherActivityThenTapBack()
-            throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
-                ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the succinct description, then the new activity
-     * starts an another activity then it finishes:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnSuccinctDescription_forwardAnotherActivityThenTapBack()
-            throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
-                ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the custom description, then the new activity
-     * stops but does not finish:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnCustomDescription_tapBackWithoutFinish() throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
-                ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the succinct description, then the new activity
-     * stops but does not finish:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnSuccinctDescription_tapBackWithoutFinish() throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
-                ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
-    }
-
-    private void saveUiRestoredAfterTappingSpanTest(
-            DescriptionType type, ViewActionActivity.ActivityCustomAction action) throws Exception {
-        startActivity();
-        // Set service.
-        enableService();
-
-        switch (type) {
-            case SUCCINCT:
-                // Set expectations with custom description.
-                sReplier.addResponse(new CannedFillResponse.Builder()
-                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                        .setSaveDescription(newDescriptionWithUrlSpan(action.toString()))
-                        .build());
-                break;
-            case CUSTOM:
-                // Set expectations with custom description.
-                sReplier.addResponse(new CannedFillResponse.Builder()
-                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                        .setSaveInfoVisitor((contexts, builder) -> builder
-                                .setCustomDescription(
-                                        newCustomDescriptionWithUrlSpan(action.toString())))
-                        .build());
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + type);
-        }
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        // Waits for the commit be processed
-        mUiBot.waitForIdle();
-
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Tapping URLSpan.
-        final URLSpan span = mUiBot.findFirstUrlSpanWithText("Here is URLSpan");
-        mActivity.syncRunOnUiThread(() -> span.onClick(/* unused= */ null));
-        // Waits for the save UI hided
-        mUiBot.waitForIdle();
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // .. check activity show up as expected
-        switch (action) {
-            case FAST_FORWARD_ANOTHER_ACTIVITY:
-                // Show up second activity.
-                SecondActivity.assertShowingDefaultMessage(mUiBot);
-                break;
-            case NORMAL_ACTIVITY:
-            case TAP_BACK_WITHOUT_FINISH:
-                // Show up view action handle activity.
-                ViewActionActivity.assertShowingDefaultMessage(mUiBot);
-                break;
-            default:
-                throw new IllegalArgumentException("invalid action: " + action);
-        }
-
-        // ..then go back and save it.
-        mUiBot.pressBack();
-        // Waits for all UI processes to complete
-        mUiBot.waitForIdle();
-
-        // Make sure previous activity is back...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // ... and tap save.
-        final UiObject2 newSaveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        mUiBot.saveForAutofill(newSaveUi, /* yesDoIt= */ true);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-
-        SecondActivity.finishIt();
-        ViewActionActivity.finishIt();
-    }
-
-    private CustomDescription newCustomDescriptionWithUrlSpan(String action) {
-        final RemoteViews presentation = newTemplate();
-        presentation.setTextViewText(R.id.custom_text, newDescriptionWithUrlSpan(action));
-        return new CustomDescription.Builder(presentation).build();
-    }
-
-    private CharSequence newDescriptionWithUrlSpan(String action) {
-        final String url = "autofillcts:" + action;
-        final SpannableString ss = new SpannableString("Here is URLSpan");
-        ss.setSpan(new URLSpan(url),
-                /* start= */ 8,  /* end= */ 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-        return ss;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java
deleted file mode 100644
index dbe072c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.TextValueSanitizer;
-import android.view.autofill.AutofillValue;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.regex.Pattern;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class TextValueSanitizerTest {
-
-    @Test
-    public void testConstructor_nullValues() {
-        assertThrows(NullPointerException.class,
-                () -> new TextValueSanitizer(Pattern.compile("42"), null));
-        assertThrows(NullPointerException.class,
-                () -> new TextValueSanitizer(null, "42"));
-    }
-
-    @Test
-    public void testSanitize_nullValue() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
-        assertThat(sanitizer.sanitize(null)).isNull();
-    }
-
-    @Test
-    public void testSanitize_nonTextValue() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
-        final AutofillValue value = AutofillValue.forToggle(true);
-        assertThat(sanitizer.sanitize(value)).isNull();
-    }
-
-    @Test
-    public void testSanitize_badRegex() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
-                "$2"); // invalid group
-        final AutofillValue value = AutofillValue.forText("blah 42  blaH");
-        assertThat(sanitizer.sanitize(value)).isNull();
-    }
-
-    @Test
-    public void testSanitize_valueMismatch() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "xxx");
-        final AutofillValue value = AutofillValue.forText("43");
-        assertThat(sanitizer.sanitize(value)).isNull();
-    }
-
-    @Test
-    public void testSanitize_simpleMatch() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"),
-                "forty-two");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("42")).getTextValue())
-            .isEqualTo("forty-two");
-    }
-
-    @Test
-    public void testSanitize_multipleMatches() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
-                "Number");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("blah 42  blaH")).getTextValue())
-            .isEqualTo("NumberNumber");
-    }
-
-    @Test
-    public void testSanitize_groupSubstitutionMatch() {
-        final TextValueSanitizer sanitizer =
-                new TextValueSanitizer(Pattern.compile("\\s*(\\d*)\\s*"), "$1");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("  42 ")).getTextValue())
-                .isEqualTo("42");
-    }
-
-    @Test
-    public void testSanitize_groupSubstitutionMatch_withOptionalGroup() {
-        final TextValueSanitizer sanitizer =
-                new TextValueSanitizer(Pattern.compile("(\\d*)\\s?(\\d*)?"), "$1$2");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("42 108")).getTextValue())
-                .isEqualTo("42108");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("42108")).getTextValue())
-                .isEqualTo("42108");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("42")).getTextValue())
-                .isEqualTo("42");
-        final TextValueSanitizer ccSanitizer = new TextValueSanitizer(Pattern.compile(
-                "^(\\d{4,5})-?\\s?(\\d{4,6})-?\\s?(\\d{4,5})" // first 3 are required
-                        + "-?\\s?((?:\\d{4,5})?)-?\\s?((?:\\d{3,5})?)$"), // last 2 are optional
-                "$1$2$3$4$5");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("1111 2222 3333 4444 5555")).getTextValue())
-                        .isEqualTo("11112222333344445555");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("11111-222222-33333-44444-55555")).getTextValue())
-                        .isEqualTo("11111222222333334444455555");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("1111 2222 3333 4444")).getTextValue())
-                        .isEqualTo("1111222233334444");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("11111-222222-33333-44444-")).getTextValue())
-                        .isEqualTo("111112222223333344444");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("1111 2222 3333")).getTextValue())
-                        .isEqualTo("111122223333");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("11111-222222-33333 ")).getTextValue())
-                        .isEqualTo("1111122222233333");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivity.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivity.java
deleted file mode 100644
index 82c4ae9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-public class TimePickerClockActivity extends AbstractTimePickerActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.time_picker_clock_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java
deleted file mode 100644
index f07d994..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "Unit test")
-public class TimePickerClockActivityTest extends TimePickerTestCase<TimePickerClockActivity> {
-
-    @Override
-    protected AutofillActivityTestRule<TimePickerClockActivity> getActivityRule() {
-        return new AutofillActivityTestRule<TimePickerClockActivity>(
-                TimePickerClockActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivity.java
deleted file mode 100644
index 0774e51..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-public class TimePickerSpinnerActivity extends AbstractTimePickerActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.time_picker_spinner_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java
deleted file mode 100644
index 9245387..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "Unit test")
-public class TimePickerSpinnerActivityTest extends TimePickerTestCase<TimePickerSpinnerActivity> {
-
-    @Override
-    protected AutofillActivityTestRule<TimePickerSpinnerActivity> getActivityRule() {
-        return new AutofillActivityTestRule<TimePickerSpinnerActivity>(
-                TimePickerSpinnerActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
deleted file mode 100644
index 480cf13..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.AbstractTimePickerActivity.ID_OUTPUT;
-import static android.autofillservice.cts.AbstractTimePickerActivity.ID_TIME_PICKER;
-import static android.autofillservice.cts.Helper.assertNumberOfChildren;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertTimeValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.icu.util.Calendar;
-
-import org.junit.Test;
-
-/**
- * Base class for {@link AbstractTimePickerActivity} tests.
- */
-abstract class TimePickerTestCase<A extends AbstractTimePickerActivity>
-        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
-
-    protected A mActivity;
-
-    @Test
-    public void testAutoFillAndSave() throws Exception {
-        assertWithMessage("subclass did not set mActivity").that(mActivity).isNotNull();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Calendar cal = Calendar.getInstance();
-        cal.set(Calendar.HOUR_OF_DAY, 4);
-        cal.set(Calendar.MINUTE, 20);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                    .setPresentation(createPresentation("Adventure Time"))
-                    .setField(ID_OUTPUT, "Y U NO CHANGE ME?")
-                    .setField(ID_TIME_PICKER, cal.getTimeInMillis())
-                    .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_TIME_PICKER)
-                .build());
-
-        mActivity.expectAutoFill("4:20", 4, 20);
-
-        // Trigger auto-fill.
-        mActivity.onOutput((v) -> v.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert properties of TimePicker field.
-        assertTextIsSanitized(fillRequest.structure, ID_TIME_PICKER);
-        assertNumberOfChildren(fillRequest.structure, ID_TIME_PICKER, 0);
-        // Auto-fill it.
-        mUiBot.selectDataset("Adventure Time");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Trigger save.
-        mActivity.setTime(10, 40);
-        mActivity.tapOk();
-
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
-
-        // Assert sanitization on save: everything should be available!
-        assertTimeValue(findNodeByResourceId(saveRequest.structure, ID_TIME_PICKER), 10, 40);
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_OUTPUT), "10:40");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
deleted file mode 100644
index a7f5c21..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import com.android.compatibility.common.util.Timeout;
-
-/**
- * Timeouts for common tasks.
- */
-public final class Timeouts {
-
-    private static final long ONE_TIMEOUT_TO_RULE_THEN_ALL_MS = 20_000;
-    private static final long ONE_NAPTIME_TO_RULE_THEN_ALL_MS = 2_000;
-
-    public static final long MOCK_IME_TIMEOUT_MS = 5_000;
-    public static final long DRAWABLE_TIMEOUT_MS = 5_000;
-
-    public static final long LONG_PRESS_MS = 3000;
-    public static final long RESPONSE_DELAY_MS = 1000;
-
-    /**
-     * Timeout until framework binds / unbinds from service.
-     */
-    public static final Timeout CONNECTION_TIMEOUT = new Timeout("CONNECTION_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout for {@link MyAutofillCallback#assertNotCalled()} - test will sleep for that amount of
-     * time as there is no callback that be received to assert it's not shown.
-     */
-    static final long CALLBACK_NOT_CALLED_TIMEOUT_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
-
-    /**
-     * Timeout until framework unbinds from a service.
-     */
-    // TODO: must be higher than RemoteFillService.TIMEOUT_IDLE_BIND_MILLIS, so we should use a
-    // @hidden @Testing constants instead...
-    static final Timeout IDLE_UNBIND_TIMEOUT = new Timeout("IDLE_UNBIND_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout to get the expected number of fill events.
-     */
-    public static final Timeout FILL_EVENTS_TIMEOUT = new Timeout("FILL_EVENTS_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout for expected autofill requests.
-     */
-    static final Timeout FILL_TIMEOUT = new Timeout("FILL_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS,
-            2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout for expected save requests.
-     */
-    static final Timeout SAVE_TIMEOUT = new Timeout("SAVE_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS,
-            2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout used when save is not expected to be shown - test will sleep for that amount of time
-     * as there is no callback that be received to assert it's not shown.
-     */
-    static final long SAVE_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
-
-    /**
-     * Timeout for UI operations. Typically used by {@link UiBot}.
-     */
-    public static final Timeout UI_TIMEOUT = new Timeout("UI_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout for a11y window change events.
-     */
-    static final long WINDOW_CHANGE_TIMEOUT_MS = ONE_TIMEOUT_TO_RULE_THEN_ALL_MS;
-
-    /**
-     * Timeout used when an a11y window change events is not expected to be generated - test will
-     * sleep for that amount of time as there is no callback that be received to assert it's not
-     * shown.
-     */
-    static final long WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
-
-    /**
-     * Timeout for webview operations. Typically used by {@link UiBot}.
-     */
-    // TODO(b/80317628): switch back to ONE_TIMEOUT_TO_RULE_THEN_ALL_MS once fixed...
-    static final Timeout WEBVIEW_TIMEOUT = new Timeout("WEBVIEW_TIMEOUT", 3_000, 2F, 5_000);
-
-    /**
-     * Timeout for showing the autofill dataset picker UI.
-     *
-     * <p>The value is usually higher than {@link #UI_TIMEOUT} because the performance of the
-     * dataset picker UI can be affect by external factors in some low-level devices.
-     *
-     * <p>Typically used by {@link UiBot}.
-     */
-    static final Timeout UI_DATASET_PICKER_TIMEOUT = new Timeout("UI_DATASET_PICKER_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout used when the dataset picker is not expected to be shown - test will sleep for that
-     * amount of time as there is no callback that be received to assert it's not shown.
-     */
-    public static final long DATASET_PICKER_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
-
-    /**
-     * Timeout (in milliseconds) for an activity to be brought out to top.
-     */
-    static final Timeout ACTIVITY_RESURRECTION = new Timeout("ACTIVITY_RESURRECTION",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout for changing the screen orientation.
-     */
-    static final Timeout UI_SCREEN_ORIENTATION_TIMEOUT = new Timeout(
-            "UI_SCREEN_ORIENTATION_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F,
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    private Timeouts() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TrampolineForResultActivity.java b/tests/autofillservice/src/android/autofillservice/cts/TrampolineForResultActivity.java
deleted file mode 100644
index 6cdd33e..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TrampolineForResultActivity.java
+++ /dev/null
@@ -1,67 +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.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity used to launch another activity for result.
- */
-// TODO: move to common code
-public class TrampolineForResultActivity extends AbstractAutoFillActivity {
-    private static final String TAG = "TrampolineForResultActivity";
-
-    private final CountDownLatch mLatch = new CountDownLatch(1);
-
-    private int mExpectedRequestCode;
-    private int mActualRequestCode;
-    private int mActualResultCode;
-
-    /**
-     * Starts an activity for result.
-     */
-    public void startForResult(Intent intent, int requestCode) {
-        mExpectedRequestCode = requestCode;
-        startActivityForResult(intent, requestCode);
-    }
-
-    /**
-     * Asserts the activity launched by {@link #startForResult(Intent, int)} was finished with the
-     * expected result code, or fails if it times out.
-     */
-    public void assertResult(int expectedResultCode) throws Exception {
-        final boolean called = mLatch.await(1000, TimeUnit.MILLISECONDS);
-        assertWithMessage("Result not received in 1s").that(called).isTrue();
-        assertWithMessage("Wrong actual code").that(mActualRequestCode)
-            .isEqualTo(mExpectedRequestCode);
-        assertWithMessage("Wrong result code").that(mActualResultCode)
-                .isEqualTo(expectedResultCode);
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        Log.d(TAG, "onActivityResult(): req=" + requestCode + ", res=" + resultCode);
-        mActualRequestCode = requestCode;
-        mActualResultCode = resultCode;
-        mLatch.countDown();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TrampolineWelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/TrampolineWelcomeActivity.java
deleted file mode 100644
index dc39808..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TrampolineWelcomeActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.content.Intent;
-import android.os.Bundle;
-
-/**
- * Activity that launches a new {@link WelcomeActivity} and finishes right away.
- */
-public class TrampolineWelcomeActivity extends AbstractAutoFillActivity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        startActivity(new Intent(this, WelcomeActivity.class));
-        finish();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
deleted file mode 100644
index 88173d3..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ /dev/null
@@ -1,1263 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.DATASET_PICKER_NOT_SHOWN_NAPTIME_MS;
-import static android.autofillservice.cts.Timeouts.LONG_PRESS_MS;
-import static android.autofillservice.cts.Timeouts.SAVE_NOT_SHOWN_NAPTIME_MS;
-import static android.autofillservice.cts.Timeouts.SAVE_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.UI_DATASET_PICKER_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.UI_SCREEN_ORIENTATION_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.UI_TIMEOUT;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.Direction;
-import android.support.test.uiautomator.SearchCondition;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiScrollable;
-import android.support.test.uiautomator.UiSelector;
-import android.support.test.uiautomator.Until;
-import android.text.Html;
-import android.text.Spanned;
-import android.text.style.URLSpan;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityWindowInfo;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.RetryableException;
-import com.android.compatibility.common.util.Timeout;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Helper for UI-related needs.
- */
-public class UiBot {
-
-    private static final String TAG = "AutoFillCtsUiBot";
-
-    private static final String RESOURCE_ID_DATASET_PICKER = "autofill_dataset_picker";
-    private static final String RESOURCE_ID_DATASET_HEADER = "autofill_dataset_header";
-    private static final String RESOURCE_ID_SAVE_SNACKBAR = "autofill_save";
-    private static final String RESOURCE_ID_SAVE_ICON = "autofill_save_icon";
-    private static final String RESOURCE_ID_SAVE_TITLE = "autofill_save_title";
-    private static final String RESOURCE_ID_CONTEXT_MENUITEM = "floating_toolbar_menu_item_text";
-    private static final String RESOURCE_ID_SAVE_BUTTON_NO = "autofill_save_no";
-    private static final String RESOURCE_ID_SAVE_BUTTON_YES = "autofill_save_yes";
-    private static final String RESOURCE_ID_OVERFLOW = "overflow";
-
-    private static final String RESOURCE_STRING_SAVE_TITLE = "autofill_save_title";
-    private static final String RESOURCE_STRING_SAVE_TITLE_WITH_TYPE =
-            "autofill_save_title_with_type";
-    private static final String RESOURCE_STRING_SAVE_TYPE_PASSWORD = "autofill_save_type_password";
-    private static final String RESOURCE_STRING_SAVE_TYPE_ADDRESS = "autofill_save_type_address";
-    private static final String RESOURCE_STRING_SAVE_TYPE_CREDIT_CARD =
-            "autofill_save_type_credit_card";
-    private static final String RESOURCE_STRING_SAVE_TYPE_USERNAME = "autofill_save_type_username";
-    private static final String RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS =
-            "autofill_save_type_email_address";
-    private static final String RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD =
-            "autofill_save_type_debit_card";
-    private static final String RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD =
-            "autofill_save_type_payment_card";
-    private static final String RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD =
-            "autofill_save_type_generic_card";
-    private static final String RESOURCE_STRING_SAVE_BUTTON_NEVER = "autofill_save_never";
-    private static final String RESOURCE_STRING_SAVE_BUTTON_NOT_NOW = "autofill_save_notnow";
-    private static final String RESOURCE_STRING_SAVE_BUTTON_NO_THANKS = "autofill_save_no";
-    private static final String RESOURCE_STRING_SAVE_BUTTON_YES = "autofill_save_yes";
-    private static final String RESOURCE_STRING_UPDATE_BUTTON_YES = "autofill_update_yes";
-    private static final String RESOURCE_STRING_CONTINUE_BUTTON_YES = "autofill_continue_yes";
-    private static final String RESOURCE_STRING_UPDATE_TITLE = "autofill_update_title";
-    private static final String RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE =
-            "autofill_update_title_with_type";
-
-    private static final String RESOURCE_STRING_AUTOFILL = "autofill";
-    private static final String RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE =
-            "autofill_picker_accessibility_title";
-    private static final String RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE =
-            "autofill_save_accessibility_title";
-
-
-    static final BySelector DATASET_PICKER_SELECTOR = By.res("android", RESOURCE_ID_DATASET_PICKER);
-    private static final BySelector SAVE_UI_SELECTOR = By.res("android", RESOURCE_ID_SAVE_SNACKBAR);
-    private static final BySelector DATASET_HEADER_SELECTOR =
-            By.res("android", RESOURCE_ID_DATASET_HEADER);
-
-    // TODO: figure out a more reliable solution that does not depend on SystemUI resources.
-    private static final String SPLIT_WINDOW_DIVIDER_ID =
-            "com.android.systemui:id/docked_divider_background";
-
-    private static final boolean DUMP_ON_ERROR = true;
-
-    /** Pass to {@link #setScreenOrientation(int)} to change the display to portrait mode */
-    public static int PORTRAIT = 0;
-
-    /** Pass to {@link #setScreenOrientation(int)} to change the display to landscape mode */
-    public static int LANDSCAPE = 1;
-
-    private final UiDevice mDevice;
-    private final Context mContext;
-    private final String mPackageName;
-    private final UiAutomation mAutoman;
-    private final Timeout mDefaultTimeout;
-
-    private boolean mOkToCallAssertNoDatasets;
-
-    public UiBot() {
-        this(UI_TIMEOUT);
-    }
-
-    public UiBot(Timeout defaultTimeout) {
-        mDefaultTimeout = defaultTimeout;
-        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        mDevice = UiDevice.getInstance(instrumentation);
-        mContext = instrumentation.getContext();
-        mPackageName = mContext.getPackageName();
-        mAutoman = instrumentation.getUiAutomation();
-    }
-
-    public void waitForIdle() {
-        final long before = SystemClock.elapsedRealtimeNanos();
-        mDevice.waitForIdle();
-        final float delta = ((float) (SystemClock.elapsedRealtimeNanos() - before)) / 1_000_000;
-        Log.v(TAG, "device idle in " + delta + "ms");
-    }
-
-    public void waitForIdleSync() {
-        final long before = SystemClock.elapsedRealtimeNanos();
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        final float delta = ((float) (SystemClock.elapsedRealtimeNanos() - before)) / 1_000_000;
-        Log.v(TAG, "device idle sync in " + delta + "ms");
-    }
-
-    public void reset() {
-        mOkToCallAssertNoDatasets = false;
-    }
-
-    /**
-     * Assumes the device has a minimum height and width of {@code minSize}, throwing a
-     * {@code AssumptionViolatedException} if it doesn't (so the test is skiped by the JUnit
-     * Runner).
-     */
-    public void assumeMinimumResolution(int minSize) {
-        final int width = mDevice.getDisplayWidth();
-        final int heigth = mDevice.getDisplayHeight();
-        final int min = Math.min(width, heigth);
-        assumeTrue("Screen size is too small (" + width + "x" + heigth + ")", min >= minSize);
-        Log.d(TAG, "assumeMinimumResolution(" + minSize + ") passed: screen size is "
-                + width + "x" + heigth);
-    }
-
-    /**
-     * Sets the screen resolution in a way that the IME doesn't interfere with the Autofill UI
-     * when the device is rotated to landscape.
-     *
-     * When called, test must call <p>{@link #resetScreenResolution()} in a {@code finally} block.
-     *
-     * @deprecated this method should not be necessarily anymore as we're using a MockIme.
-     */
-    @Deprecated
-    // TODO: remove once we're sure no more OEM is getting failure due to screen size
-    public void setScreenResolution() {
-        if (true) {
-            Log.w(TAG, "setScreenResolution(): ignored");
-            return;
-        }
-        assumeMinimumResolution(500);
-
-        runShellCommand("wm size 1080x1920");
-        runShellCommand("wm density 320");
-    }
-
-    /**
-     * Resets the screen resolution.
-     *
-     * <p>Should always be called after {@link #setScreenResolution()}.
-     *
-     * @deprecated this method should not be necessarily anymore as we're using a MockIme.
-     */
-    @Deprecated
-    // TODO: remove once we're sure no more OEM is getting failure due to screen size
-    public void resetScreenResolution() {
-        if (true) {
-            Log.w(TAG, "resetScreenResolution(): ignored");
-            return;
-        }
-        runShellCommand("wm density reset");
-        runShellCommand("wm size reset");
-    }
-
-    /**
-     * Asserts the dataset picker is not shown anymore.
-     *
-     * @throws IllegalStateException if called *before* an assertion was made to make sure the
-     * dataset picker is shown - if that's not the case, call
-     * {@link #assertNoDatasetsEver()} instead.
-     */
-    public void assertNoDatasets() throws Exception {
-        if (!mOkToCallAssertNoDatasets) {
-            throw new IllegalStateException(
-                    "Cannot call assertNoDatasets() without calling assertDatasets first");
-        }
-        mDevice.wait(Until.gone(DATASET_PICKER_SELECTOR), UI_DATASET_PICKER_TIMEOUT.ms());
-        mOkToCallAssertNoDatasets = false;
-    }
-
-    /**
-     * Asserts the dataset picker was never shown.
-     *
-     * <p>This method is slower than {@link #assertNoDatasets()} and should only be called in the
-     * cases where the dataset picker was not previous shown.
-     */
-    public void assertNoDatasetsEver() throws Exception {
-        assertNeverShown("dataset picker", DATASET_PICKER_SELECTOR,
-                DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
-    }
-
-    /**
-     * Asserts the dataset chooser is shown and contains exactly the given datasets.
-     *
-     * @return the dataset picker object.
-     */
-    public UiObject2 assertDatasets(String...names) throws Exception {
-        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
-        return assertDatasets(picker, names);
-    }
-
-    protected UiObject2 assertDatasets(UiObject2 picker, String...names) {
-        assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
-                .containsExactlyElementsIn(Arrays.asList(names)).inOrder();
-        return picker;
-    }
-
-    /**
-     * Asserts the dataset chooser is shown and contains the given datasets.
-     *
-     * @return the dataset picker object.
-     */
-    public UiObject2 assertDatasetsContains(String...names) throws Exception {
-        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
-        assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
-                .containsAllIn(Arrays.asList(names)).inOrder();
-        return picker;
-    }
-
-    /**
-     * Asserts the dataset chooser is shown and contains the given datasets, header, and footer.
-     * <p>In fullscreen, header view is not under R.id.autofill_dataset_picker.
-     *
-     * @return the dataset picker object.
-     */
-    public UiObject2 assertDatasetsWithBorders(String header, String footer, String...names)
-            throws Exception {
-        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
-        final List<String> expectedChild = new ArrayList<>();
-        if (header != null) {
-            if (Helper.isAutofillWindowFullScreen(mContext)) {
-                final UiObject2 headerView = waitForObject(DATASET_HEADER_SELECTOR,
-                        UI_DATASET_PICKER_TIMEOUT);
-                assertWithMessage("fullscreen wrong dataset header")
-                        .that(getChildrenAsText(headerView))
-                        .containsExactlyElementsIn(Arrays.asList(header)).inOrder();
-            } else {
-                expectedChild.add(header);
-            }
-        }
-        expectedChild.addAll(Arrays.asList(names));
-        if (footer != null) {
-            expectedChild.add(footer);
-        }
-        assertWithMessage("wrong elements on dataset picker").that(getChildrenAsText(picker))
-                .containsExactlyElementsIn(expectedChild).inOrder();
-        return picker;
-    }
-
-    /**
-     * Gets the text of this object children.
-     */
-    public List<String> getChildrenAsText(UiObject2 object) {
-        final List<String> list = new ArrayList<>();
-        getChildrenAsText(object, list);
-        return list;
-    }
-
-    private static void getChildrenAsText(UiObject2 object, List<String> children) {
-        final String text = object.getText();
-        if (text != null) {
-            children.add(text);
-        }
-        for (UiObject2 child : object.getChildren()) {
-            getChildrenAsText(child, children);
-        }
-    }
-
-    /**
-     * Selects a dataset that should be visible in the floating UI and does not need to wait for
-     * application become idle.
-     */
-    public void selectDataset(String name) throws Exception {
-        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
-        selectDataset(picker, name);
-    }
-
-    /**
-     * Selects a dataset that should be visible in the floating UI and waits for application become
-     * idle if needed.
-     */
-    public void selectDatasetSync(String name) throws Exception {
-        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
-        selectDataset(picker, name);
-        mDevice.waitForIdle();
-    }
-
-    /**
-     * Selects a dataset that should be visible in the floating UI.
-     */
-    public void selectDataset(UiObject2 picker, String name) {
-        final UiObject2 dataset = picker.findObject(By.text(name));
-        if (dataset == null) {
-            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(picker));
-        }
-        dataset.click();
-    }
-
-    /**
-     * Finds the suggestion by name and perform long click on suggestion to trigger attribution
-     * intent.
-     */
-    public void longPressSuggestion(String name) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Asserts the suggestion chooser is shown in the suggestion view.
-     */
-    public void assertSuggestion(String name) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Asserts the suggestion chooser is not shown in the suggestion view.
-     */
-    public void assertNoSuggestion(String name) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Scrolls the suggestion view.
-     *
-     * @param direction The direction to scroll.
-     * @param speed The speed to scroll per second.
-     */
-    public void scrollSuggestionView(Direction direction, int speed) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Selects a view by text.
-     *
-     * <p><b>NOTE:</b> when selecting an option in dataset picker is shown, prefer
-     * {@link #selectDataset(String)}.
-     */
-    public void selectByText(String name) throws Exception {
-        Log.v(TAG, "selectByText(): " + name);
-
-        final UiObject2 object = waitForObject(By.text(name));
-        object.click();
-    }
-
-    /**
-     * Asserts a text is shown.
-     *
-     * <p><b>NOTE:</b> when asserting the dataset picker is shown, prefer
-     * {@link #assertDatasets(String...)}.
-     */
-    public UiObject2 assertShownByText(String text) throws Exception {
-        return assertShownByText(text, mDefaultTimeout);
-    }
-
-    public UiObject2 assertShownByText(String text, Timeout timeout) throws Exception {
-        final UiObject2 object = waitForObject(By.text(text), timeout);
-        assertWithMessage("No node with text '%s'", text).that(object).isNotNull();
-        return object;
-    }
-
-    /**
-     * Finds a node by text, without waiting for it to be shown (but failing if it isn't).
-     */
-    @NonNull
-    public UiObject2 findRightAwayByText(@NonNull String text) throws Exception {
-        final UiObject2 object = mDevice.findObject(By.text(text));
-        assertWithMessage("no UIObject for text '%s'", text).that(object).isNotNull();
-        return object;
-    }
-
-    /**
-     * Asserts that the text is not showing for sure in the screen "as is", i.e., without waiting
-     * for it.
-     *
-     * <p>Typically called after another assertion that waits for a condition to be shown.
-     */
-    public void assertNotShowingForSure(String text) throws Exception {
-        final UiObject2 object = mDevice.findObject(By.text(text));
-        assertWithMessage("Found node with text '%s'", text).that(object).isNull();
-    }
-
-    /**
-     * Asserts a node with the given content description is shown.
-     *
-     */
-    public UiObject2 assertShownByContentDescription(String contentDescription) throws Exception {
-        final UiObject2 object = waitForObject(By.desc(contentDescription));
-        assertWithMessage("No node with content description '%s'", contentDescription).that(object)
-                .isNotNull();
-        return object;
-    }
-
-    /**
-     * Checks if a View with a certain text exists.
-     */
-    public boolean hasViewWithText(String name) {
-        Log.v(TAG, "hasViewWithText(): " + name);
-
-        return mDevice.findObject(By.text(name)) != null;
-    }
-
-    /**
-     * Selects a view by id.
-     */
-    public UiObject2 selectByRelativeId(String id) throws Exception {
-        Log.v(TAG, "selectByRelativeId(): " + id);
-        UiObject2 object = waitForObject(By.res(mPackageName, id));
-        object.click();
-        return object;
-    }
-
-    /**
-     * Asserts the id is shown on the screen.
-     */
-    public UiObject2 assertShownById(String id) throws Exception {
-        final UiObject2 object = waitForObject(By.res(id));
-        assertThat(object).isNotNull();
-        return object;
-    }
-
-    /**
-     * Asserts the id is shown on the screen, using a resource id from the test package.
-     */
-    public UiObject2 assertShownByRelativeId(String id) throws Exception {
-        return assertShownByRelativeId(id, mDefaultTimeout);
-    }
-
-    public UiObject2 assertShownByRelativeId(String id, Timeout timeout) throws Exception {
-        final UiObject2 obj = waitForObject(By.res(mPackageName, id), timeout);
-        assertThat(obj).isNotNull();
-        return obj;
-    }
-
-    /**
-     * Asserts the id is not shown on the screen anymore, using a resource id from the test package.
-     *
-     * <p><b>Note:</b> this method should only called AFTER the id was previously shown, otherwise
-     * it might pass without really asserting anything.
-     */
-    public void assertGoneByRelativeId(@NonNull String id, @NonNull Timeout timeout) {
-        assertGoneByRelativeId(/* parent = */ null, id, timeout);
-    }
-
-    public void assertGoneByRelativeId(int resId, @NonNull Timeout timeout) {
-        assertGoneByRelativeId(/* parent = */ null, getIdName(resId), timeout);
-    }
-
-    private String getIdName(int resId) {
-        return mContext.getResources().getResourceEntryName(resId);
-    }
-
-    /**
-     * Asserts the id is not shown on the parent anymore, using a resource id from the test package.
-     *
-     * <p><b>Note:</b> this method should only called AFTER the id was previously shown, otherwise
-     * it might pass without really asserting anything.
-     */
-    public void assertGoneByRelativeId(@Nullable UiObject2 parent, @NonNull String id,
-            @NonNull Timeout timeout) {
-        final SearchCondition<Boolean> condition = Until.gone(By.res(mPackageName, id));
-        final boolean gone = parent != null
-                ? parent.wait(condition, timeout.ms())
-                : mDevice.wait(condition, timeout.ms());
-        if (!gone) {
-            final String message = "Object with id '" + id + "' should be gone after "
-                    + timeout + " ms";
-            dumpScreen(message);
-            throw new RetryableException(message);
-        }
-    }
-
-    public UiObject2 assertShownByRelativeId(int resId) throws Exception {
-        return assertShownByRelativeId(getIdName(resId));
-    }
-
-    public void assertNeverShownByRelativeId(@NonNull String description, int resId, long timeout)
-            throws Exception {
-        final BySelector selector = By.res(Helper.MY_PACKAGE, getIdName(resId));
-        assertNeverShown(description, selector, timeout);
-    }
-
-    /**
-     * Asserts that a {@code selector} is not showing after {@code timeout} milliseconds.
-     */
-    protected void assertNeverShown(String description, BySelector selector, long timeout)
-            throws Exception {
-        SystemClock.sleep(timeout);
-        final UiObject2 object = mDevice.findObject(selector);
-        if (object != null) {
-            throw new AssertionError(
-                    String.format("Should not be showing %s after %dms, but got %s",
-                            description, timeout, getChildrenAsText(object)));
-        }
-    }
-
-    /**
-     * Gets the text set on a view.
-     */
-    public String getTextByRelativeId(String id) throws Exception {
-        return waitForObject(By.res(mPackageName, id)).getText();
-    }
-
-    /**
-     * Focus in the view with the given resource id.
-     */
-    public void focusByRelativeId(String id) throws Exception {
-        waitForObject(By.res(mPackageName, id)).click();
-    }
-
-    /**
-     * Sets a new text on a view.
-     */
-    public void setTextByRelativeId(String id, String newText) throws Exception {
-        waitForObject(By.res(mPackageName, id)).setText(newText);
-    }
-
-    /**
-     * Asserts the save snackbar is showing and returns it.
-     */
-    public UiObject2 assertSaveShowing(int type) throws Exception {
-        return assertSaveShowing(SAVE_TIMEOUT, type);
-    }
-
-    /**
-     * Asserts the save snackbar is showing and returns it.
-     */
-    public UiObject2 assertSaveShowing(Timeout timeout, int type) throws Exception {
-        return assertSaveShowing(null, timeout, type);
-    }
-
-    /**
-     * Asserts the save snackbar is showing with the Update message and returns it.
-     */
-    public UiObject2 assertUpdateShowing(int... types) throws Exception {
-        return assertSaveOrUpdateShowing(/* update= */ true, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
-                null, SAVE_TIMEOUT, types);
-    }
-
-    /**
-     * Presses the Back button.
-     */
-    public void pressBack() {
-        Log.d(TAG, "pressBack()");
-        mDevice.pressBack();
-    }
-
-    /**
-     * Presses the Home button.
-     */
-    public void pressHome() {
-        Log.d(TAG, "pressHome()");
-        mDevice.pressHome();
-    }
-
-    /**
-     * Asserts the save snackbar is not showing.
-     */
-    public void assertSaveNotShowing(int type) throws Exception {
-        assertNeverShown("save UI for type " + type, SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
-    }
-
-    public void assertSaveNotShowing() throws Exception {
-        assertNeverShown("save UI", SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
-    }
-
-    private String getSaveTypeString(int type) {
-        final String typeResourceName;
-        switch (type) {
-            case SAVE_DATA_TYPE_PASSWORD:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_PASSWORD;
-                break;
-            case SAVE_DATA_TYPE_ADDRESS:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_ADDRESS;
-                break;
-            case SAVE_DATA_TYPE_CREDIT_CARD:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_CREDIT_CARD;
-                break;
-            case SAVE_DATA_TYPE_USERNAME:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_USERNAME;
-                break;
-            case SAVE_DATA_TYPE_EMAIL_ADDRESS:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS;
-                break;
-            case SAVE_DATA_TYPE_DEBIT_CARD:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD;
-                break;
-            case SAVE_DATA_TYPE_PAYMENT_CARD:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD;
-                break;
-            case SAVE_DATA_TYPE_GENERIC_CARD:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD;
-                break;
-            default:
-                throw new IllegalArgumentException("Unsupported type: " + type);
-        }
-        return getString(typeResourceName);
-    }
-
-    public UiObject2 assertSaveShowing(String description, int... types) throws Exception {
-        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
-                description, SAVE_TIMEOUT, types);
-    }
-
-    public UiObject2 assertSaveShowing(String description, Timeout timeout, int... types)
-            throws Exception {
-        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
-                description, timeout, types);
-    }
-
-    public UiObject2 assertSaveShowing(int negativeButtonStyle, String description,
-            int... types) throws Exception {
-        return assertSaveOrUpdateShowing(/* update= */ false, negativeButtonStyle, description,
-                SAVE_TIMEOUT, types);
-    }
-
-    public UiObject2 assertSaveShowing(int positiveButtonStyle, int... types) throws Exception {
-        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
-                positiveButtonStyle, /* description= */ null, SAVE_TIMEOUT, types);
-    }
-
-    public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
-            String description, Timeout timeout, int... types) throws Exception {
-        return assertSaveOrUpdateShowing(update, negativeButtonStyle,
-                SaveInfo.POSITIVE_BUTTON_STYLE_SAVE, description, timeout, types);
-    }
-
-    public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
-            int positiveButtonStyle, String description, Timeout timeout, int... types)
-            throws Exception {
-
-        final UiObject2 snackbar = waitForObject(SAVE_UI_SELECTOR, timeout);
-
-        final UiObject2 titleView =
-                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_TITLE), timeout);
-        assertWithMessage("save title (%s) is not shown", RESOURCE_ID_SAVE_TITLE).that(titleView)
-                .isNotNull();
-
-        final UiObject2 iconView =
-                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_ICON), timeout);
-        assertWithMessage("save icon (%s) is not shown", RESOURCE_ID_SAVE_ICON).that(iconView)
-                .isNotNull();
-
-        final String actualTitle = titleView.getText();
-        Log.d(TAG, "save title: " + actualTitle);
-
-        final String titleId, titleWithTypeId;
-        if (update) {
-            titleId = RESOURCE_STRING_UPDATE_TITLE;
-            titleWithTypeId = RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE;
-        } else {
-            titleId = RESOURCE_STRING_SAVE_TITLE;
-            titleWithTypeId = RESOURCE_STRING_SAVE_TITLE_WITH_TYPE;
-        }
-
-        final String serviceLabel = InstrumentedAutoFillService.getServiceLabel();
-        switch (types.length) {
-            case 1:
-                final String expectedTitle = (types[0] == SAVE_DATA_TYPE_GENERIC)
-                        ? Html.fromHtml(getString(titleId, serviceLabel), 0).toString()
-                        : Html.fromHtml(getString(titleWithTypeId,
-                                getSaveTypeString(types[0]), serviceLabel), 0).toString();
-                assertThat(actualTitle).isEqualTo(expectedTitle);
-                break;
-            case 2:
-                // We cannot predict the order...
-                assertThat(actualTitle).contains(getSaveTypeString(types[0]));
-                assertThat(actualTitle).contains(getSaveTypeString(types[1]));
-                break;
-            case 3:
-                // We cannot predict the order...
-                assertThat(actualTitle).contains(getSaveTypeString(types[0]));
-                assertThat(actualTitle).contains(getSaveTypeString(types[1]));
-                assertThat(actualTitle).contains(getSaveTypeString(types[2]));
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid types: " + Arrays.toString(types));
-        }
-
-        if (description != null) {
-            final UiObject2 saveSubTitle = snackbar.findObject(By.text(description));
-            assertWithMessage("save subtitle(%s)", description).that(saveSubTitle).isNotNull();
-        }
-
-        final String positiveButtonStringId;
-        switch (positiveButtonStyle) {
-            case SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE:
-                positiveButtonStringId = RESOURCE_STRING_CONTINUE_BUTTON_YES;
-                break;
-            default:
-                positiveButtonStringId = update ? RESOURCE_STRING_UPDATE_BUTTON_YES
-                        : RESOURCE_STRING_SAVE_BUTTON_YES;
-        }
-        final String expectedPositiveButtonText = getString(positiveButtonStringId).toUpperCase();
-        final UiObject2 positiveButton = waitForObject(snackbar,
-                By.res("android", RESOURCE_ID_SAVE_BUTTON_YES), timeout);
-        assertWithMessage("wrong text on positive button")
-                .that(positiveButton.getText().toUpperCase()).isEqualTo(expectedPositiveButtonText);
-
-        final String negativeButtonStringId;
-        if (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT) {
-            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NOT_NOW;
-        } else if (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_NEVER) {
-            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NEVER;
-        } else {
-            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NO_THANKS;
-        }
-        final String expectedNegativeButtonText = getString(negativeButtonStringId).toUpperCase();
-        final UiObject2 negativeButton = waitForObject(snackbar,
-                By.res("android", RESOURCE_ID_SAVE_BUTTON_NO), timeout);
-        assertWithMessage("wrong text on negative button")
-                .that(negativeButton.getText().toUpperCase()).isEqualTo(expectedNegativeButtonText);
-
-        final String expectedAccessibilityTitle =
-                getString(RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE);
-        assertAccessibilityTitle(snackbar, expectedAccessibilityTitle);
-
-        return snackbar;
-    }
-
-    /**
-     * Taps an option in the save snackbar.
-     *
-     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
-     * @param types expected types of save info.
-     */
-    public void saveForAutofill(boolean yesDoIt, int... types) throws Exception {
-        final UiObject2 saveSnackBar = assertSaveShowing(
-                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, types);
-        saveForAutofill(saveSnackBar, yesDoIt);
-    }
-
-    public void updateForAutofill(boolean yesDoIt, int... types) throws Exception {
-        final UiObject2 saveUi = assertUpdateShowing(types);
-        saveForAutofill(saveUi, yesDoIt);
-    }
-
-    /**
-     * Taps an option in the save snackbar.
-     *
-     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
-     * @param types expected types of save info.
-     */
-    public void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types)
-            throws Exception {
-        final UiObject2 saveSnackBar = assertSaveShowing(negativeButtonStyle, null, types);
-        saveForAutofill(saveSnackBar, yesDoIt);
-    }
-
-    /**
-     * Taps the positive button in the save snackbar.
-     *
-     * @param types expected types of save info.
-     */
-    public void saveForAutofill(int positiveButtonStyle, int... types) throws Exception {
-        final UiObject2 saveSnackBar = assertSaveShowing(positiveButtonStyle, types);
-        saveForAutofill(saveSnackBar, /* yesDoIt= */ true);
-    }
-
-    /**
-     * Taps an option in the save snackbar.
-     *
-     * @param saveSnackBar Save snackbar, typically obtained through
-     *            {@link #assertSaveShowing(int)}.
-     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
-     */
-    public void saveForAutofill(UiObject2 saveSnackBar, boolean yesDoIt) {
-        final String id = yesDoIt ? "autofill_save_yes" : "autofill_save_no";
-
-        final UiObject2 button = saveSnackBar.findObject(By.res("android", id));
-        assertWithMessage("save button (%s)", id).that(button).isNotNull();
-        button.click();
-    }
-
-    /**
-     * Gets the AUTOFILL contextual menu by long pressing a text field.
-     *
-     * <p><b>NOTE:</b> this method should only be called in scenarios where we explicitly want to
-     * test the overflow menu. For all other scenarios where we want to test manual autofill, it's
-     * better to call {@code AFM.requestAutofill()} directly, because it's less error-prone and
-     * faster.
-     *
-     * @param id resource id of the field.
-     */
-    public UiObject2 getAutofillMenuOption(String id) throws Exception {
-        final UiObject2 field = waitForObject(By.res(mPackageName, id));
-        // TODO: figure out why obj.longClick() doesn't always work
-        field.click(LONG_PRESS_MS);
-
-        List<UiObject2> menuItems = waitForObjects(
-                By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
-        final String expectedText = getAutofillContextualMenuTitle();
-
-        final StringBuffer menuNames = new StringBuffer();
-
-        // Check first menu for AUTOFILL
-        for (UiObject2 menuItem : menuItems) {
-            final String menuName = menuItem.getText();
-            if (menuName.equalsIgnoreCase(expectedText)) {
-                Log.v(TAG, "AUTOFILL found in first menu");
-                return menuItem;
-            }
-            menuNames.append("'").append(menuName).append("' ");
-        }
-
-        menuNames.append(";");
-
-        // First menu does not have AUTOFILL, check overflow
-        final BySelector overflowSelector = By.res("android", RESOURCE_ID_OVERFLOW);
-
-        // Click overflow menu button.
-        final UiObject2 overflowMenu = waitForObject(overflowSelector, mDefaultTimeout);
-        overflowMenu.click();
-
-        // Wait for overflow menu to show.
-        mDevice.wait(Until.gone(overflowSelector), 1000);
-
-        menuItems = waitForObjects(
-                By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
-        for (UiObject2 menuItem : menuItems) {
-            final String menuName = menuItem.getText();
-            if (menuName.equalsIgnoreCase(expectedText)) {
-                Log.v(TAG, "AUTOFILL found in overflow menu");
-                return menuItem;
-            }
-            menuNames.append("'").append(menuName).append("' ");
-        }
-        throw new RetryableException("no '%s' on '%s'", expectedText, menuNames);
-    }
-
-    String getAutofillContextualMenuTitle() {
-        return getString(RESOURCE_STRING_AUTOFILL);
-    }
-
-    /**
-     * Gets a string from the Android resources.
-     */
-    private String getString(String id) {
-        final Resources resources = mContext.getResources();
-        final int stringId = resources.getIdentifier(id, "string", "android");
-        try {
-            return resources.getString(stringId);
-        } catch (Resources.NotFoundException e) {
-            throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
-                    + ": ", e);
-        }
-    }
-
-    /**
-     * Gets a string from the Android resources.
-     */
-    private String getString(String id, Object... formatArgs) {
-        final Resources resources = mContext.getResources();
-        final int stringId = resources.getIdentifier(id, "string", "android");
-        try {
-            return resources.getString(stringId, formatArgs);
-        } catch (Resources.NotFoundException e) {
-            throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
-                    + ": ", e);
-        }
-    }
-
-    /**
-     * Waits for and returns an object.
-     *
-     * @param selector {@link BySelector} that identifies the object.
-     */
-    private UiObject2 waitForObject(BySelector selector) throws Exception {
-        return waitForObject(selector, mDefaultTimeout);
-    }
-
-    /**
-     * Waits for and returns an object.
-     *
-     * @param parent where to find the object (or {@code null} to use device's root).
-     * @param selector {@link BySelector} that identifies the object.
-     * @param timeout timeout in ms.
-     * @param dumpOnError whether the window hierarchy should be dumped if the object is not found.
-     */
-    private UiObject2 waitForObject(UiObject2 parent, BySelector selector, Timeout timeout,
-            boolean dumpOnError) throws Exception {
-        // NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
-        try {
-            return timeout.run("waitForObject(" + selector + ")", () -> {
-                return parent != null
-                        ? parent.findObject(selector)
-                        : mDevice.findObject(selector);
-
-            });
-        } catch (RetryableException e) {
-            if (dumpOnError) {
-                dumpScreen("waitForObject() for " + selector + "on "
-                        + (parent == null ? "mDevice" : parent) + " failed");
-            }
-            throw e;
-        }
-    }
-
-    public UiObject2 waitForObject(@Nullable UiObject2 parent, @NonNull BySelector selector,
-            @NonNull Timeout timeout)
-            throws Exception {
-        return waitForObject(parent, selector, timeout, DUMP_ON_ERROR);
-    }
-
-    /**
-     * Waits for and returns an object.
-     *
-     * @param selector {@link BySelector} that identifies the object.
-     * @param timeout timeout in ms
-     */
-    protected UiObject2 waitForObject(@NonNull BySelector selector, @NonNull Timeout timeout)
-            throws Exception {
-        return waitForObject(/* parent= */ null, selector, timeout);
-    }
-
-    /**
-     * Waits for and returns a child from a parent {@link UiObject2}.
-     */
-    public UiObject2 assertChildText(UiObject2 parent, String resourceId, String expectedText)
-            throws Exception {
-        final UiObject2 child = waitForObject(parent, By.res(mPackageName, resourceId),
-                Timeouts.UI_TIMEOUT);
-        assertWithMessage("wrong text for view '%s'", resourceId).that(child.getText())
-                .isEqualTo(expectedText);
-        return child;
-    }
-
-    /**
-     * Execute a Runnable and wait for {@link AccessibilityEvent#TYPE_WINDOWS_CHANGED} or
-     * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}.
-     */
-    public AccessibilityEvent waitForWindowChange(Runnable runnable, long timeoutMillis) {
-        try {
-            return mAutoman.executeAndWaitForEvent(runnable, (AccessibilityEvent event) -> {
-                switch (event.getEventType()) {
-                    case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
-                    case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
-                        return true;
-                    default:
-                        Log.v(TAG, "waitForWindowChange(): ignoring event " + event);
-                }
-                return false;
-            }, timeoutMillis);
-        } catch (TimeoutException e) {
-            throw new WindowChangeTimeoutException(e, timeoutMillis);
-        }
-    }
-
-    public AccessibilityEvent waitForWindowChange(Runnable runnable) {
-        return waitForWindowChange(runnable, Timeouts.WINDOW_CHANGE_TIMEOUT_MS);
-    }
-
-    /**
-     * Waits for and returns a list of objects.
-     *
-     * @param selector {@link BySelector} that identifies the object.
-     * @param timeout timeout in ms
-     */
-    private List<UiObject2> waitForObjects(BySelector selector, Timeout timeout) throws Exception {
-        // NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
-        try {
-            return timeout.run("waitForObject(" + selector + ")", () -> {
-                final List<UiObject2> uiObjects = mDevice.findObjects(selector);
-                if (uiObjects != null && !uiObjects.isEmpty()) {
-                    return uiObjects;
-                }
-                return null;
-
-            });
-
-        } catch (RetryableException e) {
-            dumpScreen("waitForObjects() for " + selector + "failed");
-            throw e;
-        }
-    }
-
-    private UiObject2 findDatasetPicker(Timeout timeout) throws Exception {
-        final UiObject2 picker = waitForObject(DATASET_PICKER_SELECTOR, timeout);
-
-        final String expectedTitle = getString(RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE);
-        assertAccessibilityTitle(picker, expectedTitle);
-
-        if (picker != null) {
-            mOkToCallAssertNoDatasets = true;
-        }
-
-        return picker;
-    }
-
-    /**
-     * Asserts a given object has the expected accessibility title.
-     */
-    private void assertAccessibilityTitle(UiObject2 object, String expectedTitle) {
-        // TODO: ideally it should get the AccessibilityWindowInfo from the object, but UiAutomator
-        // does not expose that.
-        for (AccessibilityWindowInfo window : mAutoman.getWindows()) {
-            final CharSequence title = window.getTitle();
-            if (title != null && title.toString().equals(expectedTitle)) {
-                return;
-            }
-        }
-        throw new RetryableException("Title '%s' not found for %s", expectedTitle, object);
-    }
-
-    /**
-     * Sets the the screen orientation.
-     *
-     * @param orientation typically {@link #LANDSCAPE} or {@link #PORTRAIT}.
-     *
-     * @throws RetryableException if value didn't change.
-     */
-    public void setScreenOrientation(int orientation) throws Exception {
-        mAutoman.setRotation(orientation);
-
-        UI_SCREEN_ORIENTATION_TIMEOUT.run("setScreenOrientation(" + orientation + ")", () -> {
-            return getScreenOrientation() == orientation ? Boolean.TRUE : null;
-        });
-    }
-
-    /**
-     * Gets the value of the screen orientation.
-     *
-     * @return typically {@link #LANDSCAPE} or {@link #PORTRAIT}.
-     */
-    public int getScreenOrientation() {
-        return mDevice.getDisplayRotation();
-    }
-
-    /**
-     * Dumps the current view hierarchy and take a screenshot and save both locally so they can be
-     * inspected later.
-     */
-    public void dumpScreen(@NonNull String cause) {
-        try {
-            final File file = Helper.createTestFile("hierarchy.xml");
-            if (file == null) return;
-            Log.w(TAG, "Dumping window hierarchy because " + cause + " on " + file);
-            try (FileInputStream fis = new FileInputStream(file)) {
-                mDevice.dumpWindowHierarchy(file);
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "error dumping screen on " + cause, e);
-        } finally {
-            takeScreenshotAndSave();
-        }
-    }
-
-    private Rect cropScreenshotWithoutScreenDecoration(Activity activity) {
-        final WindowInsets[] inset = new WindowInsets[1];
-        final View[] rootView = new View[1];
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            rootView[0] = activity.getWindow().getDecorView();
-            inset[0] = rootView[0].getRootWindowInsets();
-        });
-        final int navBarHeight = inset[0].getStableInsetBottom();
-        final int statusBarHeight = inset[0].getStableInsetTop();
-
-        return new Rect(0, statusBarHeight, rootView[0].getWidth(),
-                rootView[0].getHeight() - navBarHeight - statusBarHeight);
-    }
-
-    // TODO(b/74358143): ideally we should take a screenshot limited by the boundaries of the
-    // activity window, so external elements (such as the clock) are filtered out and don't cause
-    // test flakiness when the contents are compared.
-    public Bitmap takeScreenshot() {
-        return takeScreenshotWithRect(null);
-    }
-
-    public Bitmap takeScreenshot(@NonNull Activity activity) {
-        // crop the screenshot without screen decoration to prevent test flakiness.
-        final Rect rect = cropScreenshotWithoutScreenDecoration(activity);
-        return takeScreenshotWithRect(rect);
-    }
-
-    private Bitmap takeScreenshotWithRect(@Nullable Rect r) {
-        final long before = SystemClock.elapsedRealtime();
-        final Bitmap bitmap = mAutoman.takeScreenshot();
-        final long delta = SystemClock.elapsedRealtime() - before;
-        Log.v(TAG, "Screenshot taken in " + delta + "ms");
-        if (r == null) {
-            return bitmap;
-        }
-        try {
-            return Bitmap.createBitmap(bitmap, r.left, r.top, r.right, r.bottom);
-        } finally {
-            if (bitmap != null) {
-                bitmap.recycle();
-            }
-        }
-    }
-
-    /**
-     * Takes a screenshot and save it in the file system for post-mortem analysis.
-     */
-    public void takeScreenshotAndSave() {
-        File file = null;
-        try {
-            file = Helper.createTestFile("screenshot.png");
-            if (file != null) {
-                Log.i(TAG, "Taking screenshot on " + file);
-                final Bitmap screenshot = takeScreenshot();
-                Helper.dumpBitmap(screenshot, file);
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Error taking screenshot and saving on " + file, e);
-        }
-    }
-
-    /**
-     * Asserts the contents of a child element.
-     *
-     * @param parent parent object
-     * @param childId (relative) resource id of the child
-     * @param assertion if {@code null}, asserts the child does not exist; otherwise, asserts the
-     * child with it.
-     */
-    public void assertChild(@NonNull UiObject2 parent, @NonNull String childId,
-            @Nullable Visitor<UiObject2> assertion) {
-        final UiObject2 child = parent.findObject(By.res(mPackageName, childId));
-        try {
-            if (assertion != null) {
-                assertWithMessage("Didn't find child with id '%s'", childId).that(child)
-                        .isNotNull();
-                try {
-                    assertion.visit(child);
-                } catch (Throwable t) {
-                    throw new AssertionError("Error on child '" + childId + "'", t);
-                }
-            } else {
-                assertWithMessage("Shouldn't find child with id '%s'", childId).that(child)
-                        .isNull();
-            }
-        } catch (RuntimeException | Error e) {
-            dumpScreen("assertChild(" + childId + ") failed: " + e);
-            throw e;
-        }
-    }
-
-    /**
-     * Waits until the window was split to show multiple activities.
-     */
-    public void waitForWindowSplit() throws Exception {
-        try {
-            assertShownById(SPLIT_WINDOW_DIVIDER_ID);
-        } catch (Exception e) {
-            final long timeout = Timeouts.ACTIVITY_RESURRECTION.ms();
-            Log.e(TAG, "Did not find window divider " + SPLIT_WINDOW_DIVIDER_ID + "; waiting "
-                    + timeout + "ms instead");
-            SystemClock.sleep(timeout);
-        }
-    }
-
-    /**
-     * Finds the first {@link URLSpan} on the current screen.
-     */
-    public URLSpan findFirstUrlSpanWithText(String str) throws Exception {
-        final List<AccessibilityNodeInfo> list = mAutoman.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(str);
-        if (list.isEmpty()) {
-            throw new AssertionError("Didn't found AccessibilityNodeInfo with " + str);
-        }
-
-        final AccessibilityNodeInfo text = list.get(0);
-        final CharSequence accessibilityTextWithSpan = text.getText();
-        if (!(accessibilityTextWithSpan instanceof Spanned)) {
-            throw new AssertionError("\"" + text.getViewIdResourceName() + "\" was not a Spanned");
-        }
-
-        final URLSpan[] spans = ((Spanned) accessibilityTextWithSpan)
-                .getSpans(0, accessibilityTextWithSpan.length(), URLSpan.class);
-        return spans[0];
-    }
-
-    public boolean scrollToTextObject(String text) {
-        UiScrollable scroller = new UiScrollable(new UiSelector().scrollable(true));
-        try {
-            // Swipe far away from the edges to avoid triggering navigation gestures
-            scroller.setSwipeDeadZonePercentage(0.25);
-            return scroller.scrollTextIntoView(text);
-        } catch (UiObjectNotFoundException e) {
-            return false;
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
deleted file mode 100644
index d93151d..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.content.Context;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.UserData;
-
-import com.android.compatibility.common.util.SettingsStateChangerRule;
-
-import com.google.common.base.Strings;
-
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class UserDataTest {
-
-    private static final Context sContext = getInstrumentation().getTargetContext();
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
-            new SettingsStateChangerRule(sContext,
-                    AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxCategoriesSizeChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "2");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "4");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMinValueChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "4");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxValueChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
-
-
-    private final String mShortValue = Strings.repeat("k", UserData.getMinValueLength() - 1);
-    private final String mLongValue = "LONG VALUE, Y U NO SHORTER"
-            + Strings.repeat("?", UserData.getMaxValueLength());
-    private final String mId = "4815162342";
-    private final String mCategoryId = "id1";
-    private final String mCategoryId2 = "id2";
-    private final String mCategoryId3 = "id3";
-    private final String mValue = mShortValue + "-1";
-    private final String mValue2 = mShortValue + "-2";
-    private final String mValue3 = mShortValue + "-3";
-    private final String mValue4 = mShortValue + "-4";
-    private final String mValue5 = mShortValue + "-5";
-
-    private UserData.Builder mBuilder;
-
-    @Before
-    public void setFixtures() {
-        mBuilder = new UserData.Builder(mId, mValue, mCategoryId);
-    }
-
-    @Test
-    public void testBuilder_invalid() {
-        assertThrows(NullPointerException.class,
-                () -> new UserData.Builder(null, mValue, mCategoryId));
-        assertThrows(IllegalArgumentException.class,
-                () -> new UserData.Builder("", mValue, mCategoryId));
-        assertThrows(NullPointerException.class,
-                () -> new UserData.Builder(mId, null, mCategoryId));
-        assertThrows(IllegalArgumentException.class,
-                () -> new UserData.Builder(mId, "", mCategoryId));
-        assertThrows(IllegalArgumentException.class,
-                () -> new UserData.Builder(mId, mShortValue, mCategoryId));
-        assertThrows(IllegalArgumentException.class,
-                () -> new UserData.Builder(mId, mLongValue, mCategoryId));
-        assertThrows(NullPointerException.class, () -> new UserData.Builder(mId, mValue, null));
-        assertThrows(IllegalArgumentException.class, () -> new UserData.Builder(mId, mValue, ""));
-    }
-
-    @Test
-    public void testAdd_invalid() {
-        assertThrows(NullPointerException.class, () -> mBuilder.add(null, mCategoryId));
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.add("", mCategoryId));
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mShortValue, mCategoryId));
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mLongValue, mCategoryId));
-        assertThrows(NullPointerException.class, () -> mBuilder.add(mValue, null));
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mValue, ""));
-    }
-
-    @Test
-    public void testAdd_duplicatedValue() {
-        assertThat(new UserData.Builder(mId, mValue, mCategoryId).add(mValue, mCategoryId).build())
-                .isNotNull();
-        assertThat(new UserData.Builder(mId, mValue, mCategoryId).add(mValue, mCategoryId2).build())
-                .isNotNull();
-    }
-
-    @Test
-    public void testAdd_maximumCategoriesReached() {
-        // Max is 2; one was added in the constructor
-        mBuilder.add(mValue2, mCategoryId2);
-        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue3, mCategoryId3));
-    }
-
-    @Test
-    public void testAdd_maximumUserDataReached() {
-        // Max is 4; one was added in the constructor
-        mBuilder.add(mValue2, mCategoryId);
-        mBuilder.add(mValue3, mCategoryId);
-        mBuilder.add(mValue4, mCategoryId2);
-        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue5, mCategoryId2));
-    }
-
-    @Test
-    public void testSetFcAlgorithm() {
-        final UserData userData = mBuilder.setFieldClassificationAlgorithm("algo_mas", null)
-                .build();
-        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo("algo_mas");
-    }
-
-    @Test
-    public void testSetFcAlgorithmForCategory_invalid() {
-        assertThrows(NullPointerException.class, () -> mBuilder
-                .setFieldClassificationAlgorithmForCategory(null, "algo_mas", null));
-    }
-
-    @Test
-    public void testSetFcAlgorithmForCateogry() {
-        final UserData userData = mBuilder.setFieldClassificationAlgorithmForCategory(
-                mCategoryId, "algo_mas", null).build();
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId)).isEqualTo(
-                "algo_mas");
-    }
-
-    @Test
-    public void testBuild_valid() {
-        final UserData userData = mBuilder.build();
-        assertThat(userData).isNotNull();
-        assertThat(userData.getId()).isEqualTo(mId);
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId)).isNull();
-    }
-
-    @Test
-    public void testGetFcAlgorithmForCategory_invalid() {
-        final UserData userData = mBuilder.setFieldClassificationAlgorithm("algo_mas", null)
-                .build();
-        assertThrows(NullPointerException.class, () -> userData
-                .getFieldClassificationAlgorithmForCategory(null));
-    }
-
-    @Test
-    public void testNoMoreInteractionsAfterBuild() {
-        testBuild_valid();
-
-        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue, mCategoryId2));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setFieldClassificationAlgorithmForCategory(mCategoryId,
-                        "algo_mas", null));
-        assertThrows(IllegalStateException.class, () -> mBuilder.build());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UsernameOnlyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/UsernameOnlyActivity.java
deleted file mode 100644
index f5c505c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/UsernameOnlyActivity.java
+++ /dev/null
@@ -1,76 +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.autofillservice.cts;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.autofill.AutofillId;
-import android.widget.Button;
-import android.widget.EditText;
-
-public final class UsernameOnlyActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "UsernameOnlyActivity";
-
-    private EditText mUsernameEditText;
-    private Button mNextButton;
-    private AutofillId mPasswordAutofillId;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(getContentView());
-
-        mUsernameEditText = findViewById(R.id.username);
-        mNextButton = findViewById(R.id.next);
-        mNextButton.setOnClickListener((v) -> next());
-    }
-
-    protected int getContentView() {
-        return R.layout.username_only_activity;
-    }
-
-    public void focusOnUsername() {
-        syncRunOnUiThread(() -> mUsernameEditText.requestFocus());
-    }
-
-    void setUsername(String username) {
-        syncRunOnUiThread(() -> mUsernameEditText.setText(username));
-    }
-
-    AutofillId getUsernameAutofillId() {
-        return mUsernameEditText.getAutofillId();
-    }
-
-    /**
-     * Sets the autofill id of the password using the intent that launches the new activity, so it's
-     * set before the view strucutre is generated.
-     */
-    void setPasswordAutofillId(AutofillId id) {
-        mPasswordAutofillId = id;
-    }
-
-    void next() {
-        final String username = mUsernameEditText.getText().toString();
-        Log.v(TAG, "Going to next screen as user " + username + " and aid " + mPasswordAutofillId);
-        final Intent intent = new Intent(this, PasswordOnlyActivity.class)
-                .putExtra(PasswordOnlyActivity.EXTRA_USERNAME, username)
-                .putExtra(PasswordOnlyActivity.EXTRA_PASSWORD_AUTOFILL_ID, mPasswordAutofillId);
-        startActivity(intent);
-        finish();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
deleted file mode 100644
index 8a34153..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.InternalValidator;
-import android.service.autofill.LuhnChecksumValidator;
-import android.service.autofill.ValueFinder;
-import android.view.View;
-import android.view.autofill.AutofillId;
-
-import org.junit.Test;
-
-/**
- * Simple integration test to verify that the UI is only shown if the validator passes.
- */
-@AppModeFull(reason = "Service-specific test")
-public class ValidatorTest extends AbstractLoginActivityTestCase {
-
-    @Test
-    public void testShowUiWhenValidatorPass() throws Exception {
-        integrationTest(true);
-    }
-
-    @Test
-    public void testDontShowUiWhenValidatorFails() throws Exception {
-        integrationTest(false);
-    }
-
-    private void integrationTest(boolean willSaveBeShown) throws Exception {
-        enableService();
-
-        final String username = willSaveBeShown ? "7992739871-3" : "4815162342-108";
-
-        // Set response
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME, ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final AutofillId usernameId =
-                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
-                    final LuhnChecksumValidator validator = new LuhnChecksumValidator(usernameId);
-                    // Validation check to make sure the validator is properly configured
-                    assertValidator(validator, usernameId, username, willSaveBeShown);
-                    builder.setValidator(validator);
-                })
-                .build());
-
-        // Trigger auto-fill
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText(username));
-        mActivity.onPassword((v) -> v.setText("pass"));
-        mActivity.tapLogin();
-
-        if (willSaveBeShown) {
-            mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-            sReplier.getNextSaveRequest();
-        } else {
-            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-        }
-    }
-
-    private void assertValidator(InternalValidator validator, AutofillId id, String text,
-            boolean valid) {
-        final ValueFinder valueFinder = mock(ValueFinder.class);
-        doReturn(text).when(valueFinder).findByAutofillId(id);
-        assertThat(validator.isValid(valueFinder)).isEqualTo(valid);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java b/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java
deleted file mode 100644
index 63e2e3c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.service.autofill.Validators.and;
-import static android.service.autofill.Validators.not;
-import static android.service.autofill.Validators.or;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.InternalValidator;
-import android.service.autofill.Validator;
-import android.service.autofill.ValueFinder;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class ValidatorsTest {
-
-    @Mock private Validator mInvalidValidator;
-    @Mock private ValueFinder mValueFinder;
-    @Mock private InternalValidator mValidValidator;
-    @Mock private InternalValidator mValidValidator2;
-
-    @Test
-    public void testAnd_null() {
-        assertThrows(NullPointerException.class, () -> and((Validator) null));
-        assertThrows(NullPointerException.class, () -> and(mValidValidator, null));
-        assertThrows(NullPointerException.class, () -> and(null, mValidValidator));
-    }
-
-    @Test
-    public void testAnd_invalid() {
-        assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator));
-        assertThrows(IllegalArgumentException.class, () -> and(mValidValidator, mInvalidValidator));
-        assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator, mValidValidator));
-    }
-
-    @Test
-    public void testAnd_firstFailed() {
-        doReturn(false).when(mValidValidator).isValid(mValueFinder);
-        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isFalse();
-        verify(mValidValidator2, never()).isValid(mValueFinder);
-    }
-
-    @Test
-    public void testAnd_firstPassedSecondFailed() {
-        doReturn(true).when(mValidValidator).isValid(mValueFinder);
-        doReturn(false).when(mValidValidator2).isValid(mValueFinder);
-        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isFalse();
-    }
-
-    @Test
-    public void testAnd_AllPassed() {
-        doReturn(true).when(mValidValidator).isValid(mValueFinder);
-        doReturn(true).when(mValidValidator2).isValid(mValueFinder);
-        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isTrue();
-    }
-
-    @Test
-    public void testOr_null() {
-        assertThrows(NullPointerException.class, () -> or((Validator) null));
-        assertThrows(NullPointerException.class, () -> or(mValidValidator, null));
-        assertThrows(NullPointerException.class, () -> or(null, mValidValidator));
-    }
-
-    @Test
-    public void testOr_invalid() {
-        assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator));
-        assertThrows(IllegalArgumentException.class, () -> or(mValidValidator, mInvalidValidator));
-        assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator, mValidValidator));
-    }
-
-    @Test
-    public void testOr_AllFailed() {
-        doReturn(false).when(mValidValidator).isValid(mValueFinder);
-        doReturn(false).when(mValidValidator2).isValid(mValueFinder);
-        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isFalse();
-    }
-
-    @Test
-    public void testOr_firstPassed() {
-        doReturn(true).when(mValidValidator).isValid(mValueFinder);
-        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isTrue();
-        verify(mValidValidator2, never()).isValid(mValueFinder);
-    }
-
-    @Test
-    public void testOr_secondPassed() {
-        doReturn(false).when(mValidValidator).isValid(mValueFinder);
-        doReturn(true).when(mValidValidator2).isValid(mValueFinder);
-        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isTrue();
-    }
-
-    @Test
-    public void testNot_null() {
-        assertThrows(IllegalArgumentException.class, () -> not(null));
-    }
-
-    @Test
-    public void testNot_invalidClass() {
-        assertThrows(IllegalArgumentException.class, () -> not(mInvalidValidator));
-    }
-
-    @Test
-    public void testNot_falseToTrue() {
-        doReturn(false).when(mValidValidator).isValid(mValueFinder);
-        final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
-        assertThat(notValidator.isValid(mValueFinder)).isTrue();
-    }
-
-    @Test
-    public void testNot_trueToFalse() {
-        doReturn(true).when(mValidValidator).isValid(mValueFinder);
-        final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
-        assertThat(notValidator.isValid(mValueFinder)).isFalse();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ViewActionActivity.java b/tests/autofillservice/src/android/autofillservice/cts/ViewActionActivity.java
deleted file mode 100644
index 58fb45b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ViewActionActivity.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.widget.TextView;
-
-/**
- * Activity that handles VIEW action.
- */
-public class ViewActionActivity extends AbstractAutoFillActivity {
-
-    private static ViewActionActivity sInstance;
-
-    private static final String TAG = "ViewActionHandleActivity";
-    static final String ID_WELCOME = "welcome";
-    static final String DEFAULT_MESSAGE = "Welcome VIEW action handle activity";
-    private boolean mHasCustomBackBehavior;
-
-    enum ActivityCustomAction {
-        NORMAL_ACTIVITY,
-        FAST_FORWARD_ANOTHER_ACTIVITY,
-        TAP_BACK_WITHOUT_FINISH
-    }
-
-    public ViewActionActivity() {
-        sInstance = this;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.welcome_activity);
-
-        final Uri data = getIntent().getData();
-        ActivityCustomAction type = ActivityCustomAction.valueOf(data.getSchemeSpecificPart());
-
-        switch (type) {
-            case FAST_FORWARD_ANOTHER_ACTIVITY:
-                startSecondActivity();
-                break;
-            case TAP_BACK_WITHOUT_FINISH:
-                mHasCustomBackBehavior = true;
-                break;
-            case NORMAL_ACTIVITY:
-            default:
-                // no-op
-        }
-
-        TextView welcome = (TextView) findViewById(R.id.welcome);
-        welcome.setText(DEFAULT_MESSAGE);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        Log.v(TAG, "Setting sInstance to null onDestroy()");
-        sInstance = null;
-    }
-
-    @Override
-    public void finish() {
-        super.finish();
-        mHasCustomBackBehavior = false;
-    }
-
-    @Override
-    public void onBackPressed() {
-        if (mHasCustomBackBehavior) {
-            moveTaskToBack(true);
-            return;
-        }
-        super.onBackPressed();
-    }
-
-    static void finishIt() {
-        if (sInstance != null) {
-            sInstance.finish();
-        }
-    }
-
-    private void startSecondActivity() {
-        final Intent intent = new Intent(this, SecondActivity.class)
-                .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
-        startActivity(intent);
-        finish();
-    }
-
-    static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
-        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
-        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
-                .isEqualTo(DEFAULT_MESSAGE);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
index 5d03c03..1444e43 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
@@ -16,17 +16,21 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.assist.AssistStructure;
+import android.autofillservice.cts.activities.ViewAttributesTestActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
 import android.platform.test.annotations.AppModeFull;
 import android.view.View;
 import android.view.autofill.AutofillValue;
 import android.widget.EditText;
 
-
 import androidx.annotation.IdRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTestActivity.java b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTestActivity.java
deleted file mode 100644
index 4003f78..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTestActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-
-public class ViewAttributesTestActivity extends AbstractAutoFillActivity {
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.view_attribute_test_activity);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java
deleted file mode 100644
index a90b840..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.VirtualContainerView.Line;
-import android.autofillservice.cts.VirtualContainerView.Line.OneTimeLineWatcher;
-import android.graphics.Canvas;
-import android.os.Bundle;
-import android.text.InputType;
-import android.widget.EditText;
-
-/**
- * A custom activity that uses {@link Canvas} to draw the following fields:
- *
- * <ul>
- *   <li>Username
- *   <li>Password
- * </ul>
- */
-public class VirtualContainerActivity extends AbstractAutoFillActivity {
-
-    static final String BLANK_VALUE = "        ";
-    static final String INITIAL_URL_BAR_VALUE = "ftp://dev.null/4/8/15/16/23/42";
-
-    EditText mUrlBar;
-    EditText mUrlBar2;
-    VirtualContainerView mCustomView;
-
-    Line mUsername;
-    Line mPassword;
-
-    private FillExpectation mExpectation;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.virtual_container_activity);
-
-        mUrlBar = findViewById(R.id.my_url_bar);
-        mUrlBar2 = findViewById(R.id.my_url_bar2);
-        mCustomView = findViewById(R.id.virtual_container_view);
-
-        mUrlBar.setText(INITIAL_URL_BAR_VALUE);
-        mUsername = mCustomView.addLine(ID_USERNAME_LABEL, "Username", ID_USERNAME, BLANK_VALUE,
-                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL);
-        mPassword = mCustomView.addLine(ID_PASSWORD_LABEL, "Password", ID_PASSWORD, BLANK_VALUE,
-                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
-    }
-
-    /**
-     * Triggers manual autofill in a given line.
-     */
-    void requestAutofill(Line line) {
-        getAutofillManager().requestAutofill(mCustomView, line.text.id, line.bounds);
-    }
-
-    /**
-     * Sets the expectation for an auto-fill request, so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    void expectAutoFill(String username, String password) {
-        mExpectation = new FillExpectation(username, password);
-        mUsername.setTextChangedListener(mExpectation.ccUsernameWatcher);
-        mPassword.setTextChangedListener(mExpectation.ccPasswordWatcher);
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, String)}.
-     */
-    void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        mExpectation.ccUsernameWatcher.assertAutoFilled();
-        mExpectation.ccPasswordWatcher.assertAutoFilled();
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final OneTimeLineWatcher ccUsernameWatcher;
-        private final OneTimeLineWatcher ccPasswordWatcher;
-
-        private FillExpectation(String username, String password) {
-            ccUsernameWatcher = mUsername.new OneTimeLineWatcher(username);
-            ccPasswordWatcher = mPassword.new OneTimeLineWatcher(password);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java
deleted file mode 100644
index 1ba98d4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java
+++ /dev/null
@@ -1,300 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.InstrumentedAutoFillServiceCompatMode.SERVICE_NAME;
-import static android.autofillservice.cts.InstrumentedAutoFillServiceCompatMode.SERVICE_PACKAGE;
-import static android.autofillservice.cts.VirtualContainerActivity.INITIAL_URL_BAR_VALUE;
-import static android.autofillservice.cts.VirtualContainerView.ID_URL_BAR;
-import static android.autofillservice.cts.VirtualContainerView.ID_URL_BAR2;
-import static android.provider.Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_GLOBAL;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.AutofillOptions;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.SaveInfo;
-
-import com.android.compatibility.common.util.SettingsStateChangerRule;
-import com.android.compatibility.common.util.SettingsUtils;
-
-import org.junit.After;
-import org.junit.ClassRule;
-import org.junit.Test;
-
-/**
- * Test case for an activity containing virtual children but using the A11Y compat mode to implement
- * the Autofill APIs.
- */
-public class VirtualContainerActivityCompatModeTest extends VirtualContainerActivityTest {
-
-    @ClassRule
-    public static final SettingsStateChangerRule sCompatModeChanger = new SettingsStateChangerRule(
-            sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
-            SERVICE_PACKAGE + "[my_url_bar]");
-
-    public VirtualContainerActivityCompatModeTest() {
-        super(true);
-    }
-
-    @After
-    public void resetCompatMode() {
-        sContext.getApplicationContext().setAutofillOptions(null);
-    }
-
-    @Override
-    protected void preActivityCreated() {
-        sContext.getApplicationContext()
-                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
-    }
-
-    @Override
-    protected void postActivityLaunched() {
-        // Set our own compat mode as well..
-        mActivity.mCustomView.setCompatMode(true);
-    }
-
-    @Override
-    protected void enableService() {
-        Helper.enableAutofillService(getContext(), SERVICE_NAME);
-    }
-
-    @Override
-    protected void disableService() {
-        Helper.disableAutofillService(getContext());
-    }
-
-    @Override
-    protected void assertUrlBarIsSanitized(ViewNode urlBar) {
-        assertTextIsSanitized(urlBar);
-        assertThat(urlBar.getWebDomain()).isEqualTo("dev.null");
-        assertThat(urlBar.getWebScheme()).isEqualTo("ftp");
-    }
-
-    @Test
-    public void testMultipleUrlBars_firstDoesNotExist() throws Exception {
-        SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
-                SERVICE_PACKAGE + "[first_am_i,my_url_bar]");
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
-                .build());
-
-        // Trigger autofill.
-        focusToUsername();
-        assertDatasetShown(mActivity.mUsername, "DUDE");
-
-        // Make sure input was sanitized.
-        final FillRequest request = sReplier.getNextFillRequest();
-        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
-
-        assertUrlBarIsSanitized(urlBar);
-    }
-
-    @Test
-    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
-    public void testMultipleUrlBars_bothExist() throws Exception {
-        SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
-                SERVICE_PACKAGE + "[my_url_bar,my_url_bar2]");
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
-                .build());
-
-        // Trigger autofill.
-        focusToUsername();
-        assertDatasetShown(mActivity.mUsername, "DUDE");
-
-        // Make sure input was sanitized.
-        final FillRequest request = sReplier.getNextFillRequest();
-        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
-        final ViewNode urlBar2 = findNodeByResourceId(request.structure, ID_URL_BAR2);
-
-        assertUrlBarIsSanitized(urlBar);
-        assertTextIsSanitized(urlBar2);
-    }
-
-    @Test
-    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
-    public void testFocusOnUrlBarIsIgnored() throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.requestFocus());
-
-        // Must force sleep, as there is no callback that we can wait upon.
-        SystemClock.sleep(Timeouts.FILL_TIMEOUT.ms());
-
-        sReplier.assertNoUnhandledFillRequests();
-    }
-
-    @Test
-    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
-    public void testUrlBarChangeIgnoredWhenServiceCanSave() throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Fill in some stuff
-        mActivity.mUsername.setText("foo");
-        focusToPasswordExpectNoWindowEvent();
-        mActivity.mPassword.setText("bar");
-
-        // Change URL bar before views become invisible
-        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
-                mActivity.mUrlBar, "http://null/dev");
-        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
-        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
-        urlWatcher.assertAutoFilled();
-
-        // Trigger save.
-        // TODO(b/76220569): ideally, save should be triggered by calling:
-        //
-        // setViewsInvisible(VisibilityIntegrationMode.OVERRIDE_IS_VISIBLE_TO_USER);
-        //
-        // But unfortunately that's not always working due to flakiness on showing the UI, hence
-        // we're forcing commit - after all, the point here is the the URL update above didn't
-        // cancel the session (which is the case on
-        // testUrlBarChangeCancelSessionWhenServiceCannotSave()
-        mActivity.getAutofillManager().commit();
-
-        // Assert UI is showing.
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-        final ViewNode urlBar = findNodeByResourceId(saveRequest.structure, ID_URL_BAR);
-
-        assertTextAndValue(username, "foo");
-        assertTextAndValue(password, "bar");
-        // Make sure it's the URL bar from initial session.
-        assertTextAndValue(urlBar, INITIAL_URL_BAR_VALUE);
-    }
-
-    @Test
-    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
-    public void testUrlBarChangeCancelSessionWhenServiceCannotSave() throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                    .setField(ID_USERNAME, "dude")
-                    .setField(ID_PASSWORD, "sweet")
-                    .setPresentation(createPresentation("The Dude"))
-                    .build())
-                // there's no SaveInfo here
-                .build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-        assertDatasetShown(mActivity.mUsername, "The Dude");
-
-        // Fill in some stuff
-        mActivity.mUsername.setText("foo");
-        focusToPasswordExpectNoWindowEvent();
-        mActivity.mPassword.setText("bar");
-
-        // Change URL bar before views become invisible
-        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
-                mActivity.mUrlBar, "http://null/dev");
-        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
-        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
-        urlWatcher.assertAutoFilled();
-
-        // Trigger save...
-        mActivity.getAutofillManager().commit();
-
-        // ... should not be triggered because the session was already canceled...
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
-    public void testUrlBarChangeCancelSessionWhenServiceReturnsNullResponse() throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Fill in some stuff
-        mActivity.mUsername.setText("foo");
-        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-        focusToPasswordExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-        mActivity.mPassword.setText("bar");
-
-        // Change URL bar before views become invisible
-        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
-                mActivity.mUrlBar, "http://null/dev");
-        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
-        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
-        urlWatcher.assertAutoFilled();
-
-        // Trigger save...
-        mActivity.getAutofillManager().commit();
-
-        // ... should not be triggered because the session was already canceled...
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
deleted file mode 100644
index 4cdf811..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
+++ /dev/null
@@ -1,808 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertTextOnly;
-import static android.autofillservice.cts.Helper.dumpStructure;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.VirtualContainerView.ID_URL_BAR;
-import static android.autofillservice.cts.VirtualContainerView.LABEL_CLASS;
-import static android.autofillservice.cts.VirtualContainerView.TEXT_CLASS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.autofillservice.cts.VirtualContainerView.Line;
-import android.autofillservice.cts.VirtualContainerView.VisibilityIntegrationMode;
-import android.graphics.Rect;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.UiObject2;
-import android.text.InputType;
-import android.view.ViewGroup;
-import android.view.autofill.AutofillManager;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Test case for an activity containing virtual children, either using the explicit Autofill APIs
- * or Compat mode.
- */
-public class VirtualContainerActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<VirtualContainerActivity> {
-
-    // TODO(b/74256300): remove when fixed it :-)
-    private static final boolean BUG_74256300_FIXED = false;
-
-    private final boolean mCompatMode;
-    private AutofillActivityTestRule<VirtualContainerActivity> mActivityRule;
-    protected VirtualContainerActivity mActivity;
-
-    public VirtualContainerActivityTest() {
-        this(false);
-    }
-
-    protected VirtualContainerActivityTest(boolean compatMode) {
-        mCompatMode = compatMode;
-    }
-
-    /**
-     * Hook for subclass to customize test before activity is created.
-     */
-    protected void preActivityCreated() {}
-
-    /**
-     * Hook for subclass to customize activity after it's launched.
-     */
-    protected void postActivityLaunched() {}
-
-    @Override
-    protected AutofillActivityTestRule<VirtualContainerActivity> getActivityRule() {
-        if (mActivityRule == null) {
-            mActivityRule = new AutofillActivityTestRule<VirtualContainerActivity>(
-                    VirtualContainerActivity.class) {
-                @Override
-                protected void beforeActivityLaunched() {
-                    preActivityCreated();
-                }
-
-                @Override
-                protected void afterActivityLaunched() {
-                    mActivity = getActivity();
-                    postActivityLaunched();
-                }
-            };
-
-        }
-        return mActivityRule;
-    }
-
-    @Test
-    public void testAutofillSync() throws Exception {
-        autofillTest(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillSync() is enough")
-    public void testAutofillAsync() throws Exception {
-        skipTestOnCompatMode();
-
-        autofillTest(false);
-    }
-
-    @Test
-    public void testAutofill_appContext() throws Exception {
-        mActivity.mCustomView.setAutofillManager(mActivity.getApplicationContext());
-        autofillTest(true);
-        // Validation check to make sure autofill is enabled in the application context
-        assertThat(mActivity.getApplicationContext().getSystemService(AutofillManager.class)
-                .isEnabled()).isTrue();
-    }
-
-    /**
-     * Focus to username and expect window event
-     */
-    void focusToUsername() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.mUsername.changeFocus(true));
-    }
-
-    /**
-     * Focus to username and expect no autofill window event
-     */
-    void focusToUsernameExpectNoWindowEvent() throws Throwable {
-        // TODO: should use waitForWindowChange() if we can filter out event of app Activity itself.
-        mActivityRule.runOnUiThread(() -> mActivity.mUsername.changeFocus(true));
-    }
-
-    /**
-     * Focus to password and expect window event
-     */
-    void focusToPassword() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.mPassword.changeFocus(true));
-    }
-
-    /**
-     * Focus to password and expect no autofill window event
-     */
-    void focusToPasswordExpectNoWindowEvent() throws Throwable {
-        // TODO should use waitForWindowChange() if we can filter out event of app Activity itself.
-        mActivityRule.runOnUiThread(() -> mActivity.mPassword.changeFocus(true));
-    }
-
-    /**
-     * Tests autofilling the virtual views, using the sync / async version of ViewStructure.addChild
-     */
-    private void autofillTest(boolean sync) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
-                .setField(ID_PASSWORD, "sweet", createPresentation("SWEET"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-        mActivity.mCustomView.setSync(sync);
-
-        // Trigger auto-fill.
-        focusToUsername();
-        assertDatasetShown(mActivity.mUsername, "DUDE");
-
-        // Play around with focus to make sure picker is properly drawn.
-        if (BUG_74256300_FIXED || !mCompatMode) {
-            focusToPassword();
-            assertDatasetShown(mActivity.mPassword, "SWEET");
-
-            focusToUsername();
-            assertDatasetShown(mActivity.mUsername, "DUDE");
-        }
-
-        // Make sure input was sanitized.
-        final FillRequest request = sReplier.getNextFillRequest();
-        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
-        final ViewNode usernameLabel = findNodeByResourceId(request.structure, ID_USERNAME_LABEL);
-        final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
-        final ViewNode passwordLabel = findNodeByResourceId(request.structure, ID_PASSWORD_LABEL);
-        final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
-
-        assertUrlBarIsSanitized(urlBar);
-        assertTextIsSanitized(username);
-        assertTextIsSanitized(password);
-        assertLabel(usernameLabel, "Username");
-        assertLabel(passwordLabel, "Password");
-
-        assertThat(usernameLabel.getClassName()).isEqualTo(LABEL_CLASS);
-        assertThat(username.getClassName()).isEqualTo(TEXT_CLASS);
-        assertThat(passwordLabel.getClassName()).isEqualTo(LABEL_CLASS);
-        assertThat(password.getClassName()).isEqualTo(TEXT_CLASS);
-
-        assertThat(username.getIdEntry()).isEqualTo(ID_USERNAME);
-        assertThat(password.getIdEntry()).isEqualTo(ID_PASSWORD);
-
-        assertThat(username.getInputType())
-                .isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL);
-        assertThat(usernameLabel.getInputType()).isEqualTo(0);
-        assertThat(password.getInputType())
-                .isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
-        assertThat(passwordLabel.getInputType()).isEqualTo(0);
-
-        final String[] autofillHints = username.getAutofillHints();
-        final boolean hasCompatModeFlag = (request.flags
-                & android.service.autofill.FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST) != 0;
-        if (mCompatMode) {
-            assertThat(hasCompatModeFlag).isTrue();
-            assertThat(autofillHints).isNull();
-            assertThat(username.getHtmlInfo()).isNull();
-            assertThat(password.getHtmlInfo()).isNull();
-        } else {
-            assertThat(hasCompatModeFlag).isFalse();
-            // Make sure order is preserved and dupes not removed.
-            assertThat(autofillHints).asList()
-                    .containsExactly("c", "a", "a", "b", "a", "a")
-                    .inOrder();
-            try {
-                VirtualContainerView.assertHtmlInfo(username);
-                VirtualContainerView.assertHtmlInfo(password);
-            } catch (AssertionError | RuntimeException e) {
-                dumpStructure("HtmlInfo failed", request.structure);
-                throw e;
-            }
-        }
-
-        // Make sure initial focus was properly set.
-        assertWithMessage("Username node is not focused").that(username.isFocused()).isTrue();
-        assertWithMessage("Password node is focused").that(password.isFocused()).isFalse();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("DUDE");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillSync() is enough")
-    public void testAutofillTwoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE")
-                        .setField(ID_PASSWORD, "SWEET")
-                        .setPresentation(createPresentation("THE DUDE"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("DUDE", "SWEET");
-
-        // Trigger auto-fill.
-        focusToUsername();
-        sReplier.getNextFillRequest();
-        assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
-
-        // Play around with focus to make sure picker is properly drawn.
-        if (BUG_74256300_FIXED || !mCompatMode) {
-            focusToPassword();
-            assertDatasetShown(mActivity.mPassword, "The Dude", "THE DUDE");
-            focusToUsername();
-            assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
-        }
-
-        // Auto-fill it.
-        mUiBot.selectDataset("THE DUDE");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofillOverrideDispatchProvideAutofillStructure() throws Exception {
-        mActivity.mCustomView.setOverrideDispatchProvideAutofillStructure(true);
-        autofillTest(true);
-    }
-
-    @Test
-    public void testAutofillManuallyOneDataset() throws Exception {
-        skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.requestAutofill(mActivity.mUsername);
-        sReplier.getNextFillRequest();
-
-        // Select datatest.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception {
-        autofillManuallyTwoDatasets(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception {
-        autofillManuallyTwoDatasets(false);
-    }
-
-    private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
-        skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "jenny")
-                        .setField(ID_PASSWORD, "8675309")
-                        .setPresentation(createPresentation("Jenny"))
-                        .build())
-                .build());
-        if (pickFirst) {
-            mActivity.expectAutoFill("dude", "sweet");
-        } else {
-            mActivity.expectAutoFill("jenny", "8675309");
-
-        }
-
-        // Trigger auto-fill.
-        mActivity.getSystemService(AutofillManager.class).requestAutofill(
-                mActivity.mCustomView, mActivity.mUsername.text.id,
-                mActivity.mUsername.getAbsCoordinates());
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        final UiObject2 picker = assertDatasetShown(mActivity.mUsername, "The Dude", "Jenny");
-        mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofillCallbacks() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        focusToUsername();
-        sReplier.getNextFillRequest();
-
-        callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-
-        // Change focus
-        focusToPassword();
-        callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-        callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mPassword.text.id);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackDisabled() throws Throwable {
-        // Set service.
-        disableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-
-        // Assert callback was called
-        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackNoDatasets() throws Throwable {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger autofill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        mUiBot.assertNoDatasetsEver();
-
-        // Assert callback was called
-        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackNoDatasetsButSaveInfo() throws Throwable {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger autofill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Autofill it.
-        mUiBot.assertNoDatasetsEver();
-
-        // Assert callback was called
-        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-
-        // Make sure save is not triggered
-        mActivity.getAutofillManager().commit();
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    public void testSaveDialogNotShownWhenBackIsPressed() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        focusToUsername();
-        sReplier.getNextFillRequest();
-        assertDatasetShown(mActivity.mUsername, "The Dude");
-
-        mUiBot.pressBack();
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    public void testSave_childViewsGone_notifyAfm() throws Throwable {
-        saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API);
-    }
-
-    @Test
-    public void testSave_childViewsGone_updateView() throws Throwable {
-        saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API);
-    }
-
-    @Test
-    @Ignore("Disabled until b/73493342 is fixed")
-    public void testSave_parentViewGone() throws Throwable {
-        saveTest(CommitType.PARENT_VIEW_GONE);
-    }
-
-    @Test
-    public void testSave_appCallsCommit() throws Throwable {
-        saveTest(CommitType.EXPLICIT_COMMIT);
-    }
-
-    @Test
-    public void testSave_submitButtonClicked() throws Throwable {
-        saveTest(CommitType.SUBMIT_BUTTON_CLICKED);
-    }
-
-    enum CommitType {
-        CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API,
-        CHILDREN_VIEWS_GONE_IS_VISIBLE_API,
-        PARENT_VIEW_GONE,
-        EXPLICIT_COMMIT,
-        SUBMIT_BUTTON_CLICKED
-    }
-
-    private void saveTest(CommitType commitType) throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD);
-
-        switch (commitType) {
-            case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API:
-            case CHILDREN_VIEWS_GONE_IS_VISIBLE_API:
-            case PARENT_VIEW_GONE:
-                response.setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
-                break;
-            case EXPLICIT_COMMIT:
-                // does nothing
-                break;
-            case SUBMIT_BUTTON_CLICKED:
-                response
-                    .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
-                    .setSaveTriggerId(mActivity.mCustomView.mLoginButtonId);
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + commitType);
-        }
-        sReplier.addResponse(response.build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Fill in some stuff
-        mActivity.mUsername.setText("foo");
-        focusToPasswordExpectNoWindowEvent();
-        mActivity.mPassword.setText("bar");
-
-        // Trigger save.
-        switch (commitType) {
-            case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API:
-                setViewsInvisible(VisibilityIntegrationMode.NOTIFY_AFM);
-                break;
-            case CHILDREN_VIEWS_GONE_IS_VISIBLE_API:
-                setViewsInvisible(VisibilityIntegrationMode.OVERRIDE_IS_VISIBLE_TO_USER);
-                break;
-            case PARENT_VIEW_GONE:
-                mActivity.runOnUiThread(() -> {
-                    final ViewGroup parent = (ViewGroup) mActivity.mCustomView.getParent();
-                    parent.removeView(mActivity.mCustomView);
-                });
-                break;
-            case EXPLICIT_COMMIT:
-                mActivity.getAutofillManager().commit();
-                break;
-            case SUBMIT_BUTTON_CLICKED:
-                mActivity.mCustomView.clickLogin();
-                break;
-            default:
-                throw new IllegalArgumentException("unknown type: " + commitType);
-        }
-
-        // Assert UI is showing.
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-
-        assertTextAndValue(username, "foo");
-        assertTextAndValue(password, "bar");
-    }
-
-    protected void setViewsInvisible(VisibilityIntegrationMode mode) {
-        mActivity.mUsername.setVisibilityIntegrationMode(mode);
-        mActivity.mPassword.setVisibilityIntegrationMode(mode);
-        mActivity.mUsername.changeVisibility(false);
-        mActivity.mPassword.changeVisibility(false);
-    }
-
-    // NOTE: tests where save is not shown only makes sense when calling commit() explicitly,
-    // otherwise the test could pass but the UI is still shown *after* the app is committed.
-    // We could still test them by explicitly committing and then checking that the Save UI is not
-    // shown again, but then we wouldn't be effectively testing that the context was committed
-
-    @Test
-    public void testSaveNotShown_noUserInput() throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.getAutofillManager().commit();
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
-    public void testSaveNotShown_initialValues_noUserInput() throws Throwable {
-        // Prepare activitiy.
-        mActivity.mUsername.setText("foo");
-        mActivity.mPassword.setText("bar");
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.getAutofillManager().commit();
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
-    public void testSaveNotShown_initialValues_noUserInput_serviceDatasets() throws Throwable {
-        // Prepare activitiy.
-        mActivity.mUsername.setText("foo");
-        mActivity.mPassword.setText("bar");
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-        assertDatasetShown(mActivity.mUsername, "The Dude");
-
-        // Trigger save.
-        mActivity.getAutofillManager().commit();
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
-    public void testSaveNotShown_userInputMatchesDatasets() throws Throwable {
-        // Prepare activitiy.
-        mActivity.mUsername.setText("foo");
-        mActivity.mPassword.setText("bar");
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "foo")
-                        .setField(ID_PASSWORD, "bar")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-        assertDatasetShown(mActivity.mUsername, "The Dude");
-
-        // Trigger save.
-        mActivity.getAutofillManager().commit();
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    public void testDatasetFiltering() throws Throwable {
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(createPresentation(aa))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(createPresentation(ab))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "b")
-                        .setPresentation(createPresentation(b))
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        assertDatasetShown(mActivity.mUsername, aa, ab, b);
-
-        // Only two datasets start with 'a'
-        mActivity.mUsername.setText("a");
-        assertDatasetShown(mActivity.mUsername, aa, ab);
-
-        // Only one dataset start with 'aa'
-        mActivity.mUsername.setText("aa");
-        assertDatasetShown(mActivity.mUsername, aa);
-
-        // Only two datasets start with 'a'
-        mActivity.mUsername.setText("a");
-        assertDatasetShown(mActivity.mUsername, aa, ab);
-
-        // With no filter text all datasets should be shown
-        mActivity.mUsername.setText("");
-        assertDatasetShown(mActivity.mUsername, aa, ab, b);
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        mActivity.mUsername.setText("aaa");
-        callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-        mUiBot.assertNoDatasets();
-    }
-
-    /**
-     * Asserts the dataset picker is properly displayed in a give line.
-     */
-    protected UiObject2 assertDatasetShown(Line line, String... expectedDatasets)
-            throws Exception {
-        boolean autofillViewBoundsMatches = !Helper.isAutofillWindowFullScreen(mContext);
-        final UiObject2 datasetPicker = mUiBot.assertDatasets(expectedDatasets);
-        final Rect pickerBounds = datasetPicker.getVisibleBounds();
-        final Rect fieldBounds = line.getAbsCoordinates();
-        if (autofillViewBoundsMatches) {
-            assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds,
-                    fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom);
-            assertWithMessage("horizontal coordinates don't match; picker=%s, field=%s",
-                    pickerBounds, fieldBounds).that(pickerBounds.left).isEqualTo(fieldBounds.left);
-        }
-        return datasetPicker;
-    }
-
-    protected void assertLabel(ViewNode node, String expectedValue) {
-        if (mCompatMode) {
-            // Compat mode doesn't set AutofillValue of non-editable fields
-            assertTextOnly(node, expectedValue);
-        } else {
-            assertTextAndValue(node, expectedValue);
-        }
-    }
-
-    protected void assertUrlBarIsSanitized(ViewNode urlBar) {
-        assertTextIsSanitized(urlBar);
-        assertThat(urlBar.getWebDomain()).isNull();
-        assertThat(urlBar.getWebScheme()).isNull();
-    }
-
-
-    private void skipTestOnCompatMode() {
-        assumeTrue("test not applicable when on compat mode", !mCompatMode);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
deleted file mode 100644
index 6634ec0..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
+++ /dev/null
@@ -1,604 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewStructure;
-import android.view.ViewStructure.HtmlInfo;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillManager;
-import android.view.autofill.AutofillValue;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-class VirtualContainerView extends View {
-
-    private static final String TAG = "VirtualContainerView";
-    private static final int LOGIN_BUTTON_VIRTUAL_ID = 666;
-
-    static final String LABEL_CLASS = "my.readonly.view";
-    static final String TEXT_CLASS = "my.editable.view";
-    static final String ID_URL_BAR = "my_url_bar";
-    static final String ID_URL_BAR2 = "my_url_bar2";
-
-    private final ArrayList<Line> mLines = new ArrayList<>();
-    private final SparseArray<Item> mItems = new SparseArray<>();
-    private AutofillManager mAfm;
-    final AutofillId mLoginButtonId;
-
-    private Line mFocusedLine;
-    private int mNextChildId;
-
-    private Paint mTextPaint;
-    private int mTextHeight;
-    private int mTopMargin;
-    private int mLeftMargin;
-    private int mVerticalGap;
-    private int mLineLength;
-    private int mFocusedColor;
-    private int mUnfocusedColor;
-    private boolean mSync = true;
-    private boolean mOverrideDispatchProvideAutofillStructure = false;
-
-    private boolean mCompatMode = false;
-    private AccessibilityDelegate mAccessibilityDelegate;
-    private AccessibilityNodeProvider mAccessibilityNodeProvider;
-
-    /**
-     * Enum defining how the view communicate visibility changes to the framework
-     */
-    enum VisibilityIntegrationMode {
-        NOTIFY_AFM,
-        OVERRIDE_IS_VISIBLE_TO_USER
-    }
-
-    private VisibilityIntegrationMode mVisibilityIntegrationMode;
-
-    public VirtualContainerView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        setAutofillManager(context);
-
-        mTextPaint = new Paint();
-
-        mUnfocusedColor = Color.BLACK;
-        mFocusedColor = Color.RED;
-        mTextPaint.setStyle(Style.FILL);
-        DisplayMetrics metrics = new DisplayMetrics();
-        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        wm.getDefaultDisplay().getMetrics(metrics);
-        mTopMargin = metrics.heightPixels * 3 / 100;
-        mLeftMargin = metrics.widthPixels * 3 / 100;
-        mTextHeight = metrics.widthPixels * 3 / 100; // adjust text size with display width
-        mVerticalGap = metrics.heightPixels / 100;
-
-        mLineLength = mTextHeight + mVerticalGap;
-        mTextPaint.setTextSize(mTextHeight);
-        Log.d(TAG, "Text height: " + mTextHeight);
-        mLoginButtonId = new AutofillId(getAutofillId(), LOGIN_BUTTON_VIRTUAL_ID);
-    }
-
-    public void setAutofillManager(Context context) {
-        mAfm = context.getSystemService(AutofillManager.class);
-        Log.d(TAG, "Set AFM from " + context);
-    }
-
-    @Override
-    public void autofill(SparseArray<AutofillValue> values) {
-        Log.d(TAG, "autofill: " + values);
-        if (mCompatMode) {
-            Log.v(TAG, "using super.autofill() on compat mode");
-            super.autofill(values);
-            return;
-        }
-        for (int i = 0; i < values.size(); i++) {
-            final int id = values.keyAt(i);
-            final AutofillValue value = values.valueAt(i);
-            final Item item = getItem(id);
-            item.autofill(value.getTextValue());
-        }
-        postInvalidate();
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        Log.d(TAG, "onDraw: " + mLines.size() + " lines; canvas:" + canvas);
-        float x;
-        float y = mTopMargin + mLineLength;
-        for (int i = 0; i < mLines.size(); i++) {
-            x = mLeftMargin;
-            final Line line = mLines.get(i);
-            if (!line.visible) {
-                continue;
-            }
-            Log.v(TAG, "Drawing '" + line + "' at " + x + "x" + y);
-            mTextPaint.setColor(line.focused ? mFocusedColor : mUnfocusedColor);
-            final String readOnlyText = line.label.text + ":  [";
-            final String writeText = line.text.text + "]";
-            // Paints the label first...
-            canvas.drawText(readOnlyText, x, y, mTextPaint);
-            // ...then paints the edit text and sets the proper boundary
-            final float deltaX = mTextPaint.measureText(readOnlyText);
-            x += deltaX;
-            line.bounds.set((int) x, (int) (y - mLineLength),
-                    (int) (x + mTextPaint.measureText(writeText)), (int) y);
-            Log.d(TAG, "setBounds(" + x + ", " + y + "): " + line.bounds);
-            canvas.drawText(writeText, x, y, mTextPaint);
-            y += mLineLength;
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        final int y = (int) event.getY();
-        Log.d(TAG, "You can touch this: y=" + y + ", range=" + mLineLength + ", top=" + mTopMargin);
-        int lowerY = mTopMargin;
-        int upperY = -1;
-        for (int i = 0; i < mLines.size(); i++) {
-            upperY = lowerY + mLineLength;
-            final Line line = mLines.get(i);
-            Log.d(TAG, "Line " + i + " ranges from " + lowerY + " to " + upperY);
-            if (lowerY <= y && y <= upperY) {
-                if (mFocusedLine != null) {
-                    Log.d(TAG, "Removing focus from " + mFocusedLine);
-                    mFocusedLine.changeFocus(false);
-                }
-                Log.d(TAG, "Changing focus to " + line);
-                mFocusedLine = line;
-                mFocusedLine.changeFocus(true);
-                invalidate();
-                break;
-            }
-            lowerY += mLineLength;
-        }
-        return super.onTouchEvent(event);
-    }
-
-    @Override
-    public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) {
-        if (mOverrideDispatchProvideAutofillStructure) {
-            Log.d(TAG, "Overriding dispatchProvideAutofillStructure()");
-            structure.setAutofillId(getAutofillId());
-            onProvideAutofillVirtualStructure(structure, flags);
-        } else {
-            super.dispatchProvideAutofillStructure(structure, flags);
-        }
-    }
-
-    @Override
-    public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
-        Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags);
-        super.onProvideAutofillVirtualStructure(structure, flags);
-
-        if (mCompatMode) {
-            Log.v(TAG, "using super.onProvideAutofillVirtualStructure() on compat mode");
-            return;
-        }
-
-        final String packageName = getContext().getPackageName();
-        structure.setClassName(getClass().getName());
-        final int childrenSize = mItems.size();
-        int index = structure.addChildCount(childrenSize);
-        final String syncMsg = mSync ? "" : " (async)";
-        for (int i = 0; i < childrenSize; i++) {
-            final Item item = mItems.valueAt(i);
-            Log.d(TAG, "Adding new child" + syncMsg + " at index " + index + ": " + item);
-            final ViewStructure child = mSync
-                    ? structure.newChild(index)
-                    : structure.asyncNewChild(index);
-            child.setAutofillId(structure.getAutofillId(), item.id);
-            child.setDataIsSensitive(item.sensitive);
-            if (item.editable) {
-                child.setInputType(item.line.inputType);
-            }
-            index++;
-            child.setClassName(item.className);
-            // Must set "fake" idEntry because that's what the test cases use to find nodes.
-            child.setId(1000 + index, packageName, "id", item.resourceId);
-            child.setText(item.text);
-            if (TextUtils.getTrimmedLength(item.text) > 0) {
-                // TODO: Must checked trimmed length because input fields use 8 empty spaces to
-                // set width
-                child.setAutofillValue(AutofillValue.forText(item.text));
-            }
-            child.setFocused(item.line.focused);
-            child.setHtmlInfo(child.newHtmlInfoBuilder("TAGGY")
-                    .addAttribute("a1", "v1")
-                    .addAttribute("a2", "v2")
-                    .addAttribute("a1", "v2")
-                    .build());
-            child.setAutofillHints(new String[] {"c", "a", "a", "b", "a", "a"});
-
-            if (!mSync) {
-                Log.d(TAG, "Commiting virtual child");
-                child.asyncCommit();
-            }
-        }
-    }
-
-    @Override
-    public boolean isVisibleToUserForAutofill(int virtualId) {
-        boolean callSuper = true;
-        if (mVisibilityIntegrationMode == null) {
-            Log.w(TAG, "isVisibleToUserForAutofill(): mVisibilityIntegrationMode not set");
-        } else {
-            callSuper = mVisibilityIntegrationMode == VisibilityIntegrationMode.NOTIFY_AFM;
-        }
-        final boolean isVisible;
-        if (callSuper) {
-            isVisible = super.isVisibleToUserForAutofill(virtualId);
-            Log.d(TAG, "isVisibleToUserForAutofill(" + virtualId + ") using super: " + isVisible);
-        } else {
-            final Item item = getItem(virtualId);
-            isVisible = item.line.visible;
-            Log.d(TAG, "isVisibleToUserForAutofill(" + virtualId + ") set by test: " + isVisible);
-        }
-        return isVisible;
-    }
-
-    /**
-     * Emulates clicking the login button.
-     */
-    void clickLogin() {
-        Log.d(TAG, "clickLogin()");
-        if (mCompatMode) {
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED, LOGIN_BUTTON_VIRTUAL_ID);
-        } else {
-            mAfm.notifyViewClicked(this, LOGIN_BUTTON_VIRTUAL_ID);
-        }
-    }
-
-    private Item getItem(int id) {
-        final Item item = mItems.get(id);
-        assertWithMessage("No item for id %s", id).that(item).isNotNull();
-        return item;
-    }
-
-    private AccessibilityNodeInfo onProvideAutofillCompatModeAccessibilityNodeInfo() {
-        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
-
-        final String packageName = getContext().getPackageName();
-        node.setPackageName(packageName);
-        node.setClassName(getClass().getName());
-
-        final int childrenSize = mItems.size();
-        for (int i = 0; i < childrenSize; i++) {
-            final Item item = mItems.valueAt(i);
-            final int id = i + 1;
-            Log.d(TAG, "Adding new A11Y child with id " + id + ": " + item);
-
-            node.addChild(this, id);
-        }
-
-        return node;
-    }
-
-    private AccessibilityNodeInfo onProvideAutofillCompatModeAccessibilityNodeInfoForLoginButton() {
-        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
-        node.setSource(this, LOGIN_BUTTON_VIRTUAL_ID);
-        node.setPackageName(getContext().getPackageName());
-        // TODO(b/37566627): ideally this button should be visible / drawn in the canvas and contain
-        // more properties like boundaries, class name, text etc...
-        return node;
-    }
-
-    static void assertHtmlInfo(ViewNode node) {
-        final String name = node.getText().toString();
-        final HtmlInfo info = node.getHtmlInfo();
-        assertWithMessage("no HTML info on %s", name).that(info).isNotNull();
-        assertWithMessage("wrong HTML tag on %s", name).that(info.getTag()).isEqualTo("TAGGY");
-        assertWithMessage("wrong attributes on %s", name).that(info.getAttributes())
-                .containsExactly(
-                        new Pair<>("a1", "v1"),
-                        new Pair<>("a2", "v2"),
-                        new Pair<>("a1", "v2"));
-    }
-
-    Line addLine(String labelId, String label, String textId, String text, int inputType) {
-        final Line line = new Line(labelId, label, textId, text, inputType);
-        Log.d(TAG, "addLine: " + line);
-        mLines.add(line);
-        mItems.put(line.label.id, line.label);
-        mItems.put(line.text.id, line.text);
-        return line;
-    }
-
-    void setSync(boolean sync) {
-        mSync = sync;
-    }
-
-    void setCompatMode(boolean compatMode) {
-        mCompatMode = compatMode;
-
-        if (mCompatMode) {
-            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-            mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
-                @Override
-                public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
-                    Log.d(TAG, "createAccessibilityNodeInfo(): id=" + virtualViewId);
-                    switch (virtualViewId) {
-                        case AccessibilityNodeProvider.HOST_VIEW_ID:
-                            return onProvideAutofillCompatModeAccessibilityNodeInfo();
-                        case LOGIN_BUTTON_VIRTUAL_ID:
-                            return onProvideAutofillCompatModeAccessibilityNodeInfoForLoginButton();
-                        default:
-                            final Item item = getItem(virtualViewId);
-                            return item.provideAccessibilityNodeInfo(VirtualContainerView.this,
-                                    getContext());
-                    }
-                }
-
-                @Override
-                public boolean performAction(int virtualViewId, int action, Bundle arguments) {
-                    if (action == AccessibilityNodeInfo.ACTION_SET_TEXT) {
-                        final CharSequence text = arguments.getCharSequence(
-                                AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
-                        final Item item = getItem(virtualViewId);
-                        item.autofill(text);
-                        return true;
-                    }
-
-                    return false;
-                }
-            };
-            mAccessibilityDelegate = new AccessibilityDelegate() {
-                @Override
-                public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
-                    return mAccessibilityNodeProvider;
-                }
-            };
-
-            setAccessibilityDelegate(mAccessibilityDelegate);
-        }
-    }
-
-    void setOverrideDispatchProvideAutofillStructure(boolean flag) {
-        mOverrideDispatchProvideAutofillStructure = flag;
-    }
-
-    private void sendAccessibilityEvent(int eventType, int virtualId) {
-        final AccessibilityEvent event = AccessibilityEvent.obtain();
-        event.setEventType(eventType);
-        event.setSource(VirtualContainerView.this, virtualId);
-        event.setEnabled(true);
-        event.setPackageName(getContext().getPackageName());
-        Log.v(TAG, "sendAccessibilityEvent(" + eventType + ", " + virtualId + "): " + event);
-        getContext().getSystemService(AccessibilityManager.class).sendAccessibilityEvent(event);
-    }
-
-    final class Line {
-
-        final Item label;
-        final Item text;
-        // Boundaries of the text field, relative to the CustomView
-        final Rect bounds = new Rect();
-        // Boundaries of the text field, relative to the screen
-        Rect absBounds;
-
-        private boolean focused;
-        private boolean visible = true;
-        private final int inputType;
-
-        private Line(String labelId, String label, String textId, String text, int inputType) {
-            this.label = new Item(this, ++mNextChildId, labelId, label, false, false);
-            this.text = new Item(this, ++mNextChildId, textId, text, true, true);
-            this.inputType = inputType;
-        }
-
-        void changeFocus(boolean focused) {
-            this.focused = focused;
-
-            if (focused) {
-                absBounds = getAbsCoordinates();
-                Log.v(TAG, "Setting absBounds for " + text.id + " on focus change: " + absBounds);
-            }
-
-            if (mCompatMode) {
-                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED, text.id);
-                return;
-            }
-
-            if (focused) {
-                Log.d(TAG, "focus gained on " + text.id + "; absBounds=" + absBounds);
-                mAfm.notifyViewEntered(VirtualContainerView.this, text.id, absBounds);
-            } else {
-                Log.d(TAG, "focus lost on " + text.id);
-                mAfm.notifyViewExited(VirtualContainerView.this, text.id);
-            }
-        }
-
-        void setVisibilityIntegrationMode(VisibilityIntegrationMode mode) {
-            mVisibilityIntegrationMode = mode;
-        }
-
-        void changeVisibility(boolean visible) {
-            if (mVisibilityIntegrationMode == null) {
-                throw new IllegalStateException("must call setVisibilityIntegrationMode() first");
-            }
-            if (this.visible == visible) {
-                return;
-            }
-            this.visible = visible;
-            Log.d(TAG, "visibility changed view: " + text.id + "; visible:" + visible
-                    + "; integrationMode: " + mVisibilityIntegrationMode);
-            if (mVisibilityIntegrationMode == VisibilityIntegrationMode.NOTIFY_AFM) {
-                mAfm.notifyViewVisibilityChanged(VirtualContainerView.this, text.id, visible);
-            }
-            invalidate();
-        }
-
-        Rect getAbsCoordinates() {
-            // Must offset the boundaries so they're relative to the CustomView.
-            final int offset[] = new int[2];
-            getLocationOnScreen(offset);
-            final Rect absBounds = new Rect(bounds.left + offset[0],
-                    bounds.top + offset[1],
-                    bounds.right + offset[0], bounds.bottom + offset[1]);
-            Log.v(TAG, "getAbsCoordinates() for " + text.id + ": bounds=" + bounds
-                    + " offset: " + Arrays.toString(offset) + " absBounds: " + absBounds);
-            return absBounds;
-        }
-
-        void setText(String value) {
-            text.text = value;
-            final AutofillManager autofillManager =
-                    getContext().getSystemService(AutofillManager.class);
-            if (mCompatMode) {
-                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, text.id);
-            } else {
-                if (autofillManager != null) {
-                    autofillManager.notifyValueChanged(VirtualContainerView.this, text.id,
-                            AutofillValue.forText(text.text));
-                }
-            }
-            invalidate();
-        }
-
-        void setTextChangedListener(TextWatcher listener) {
-            text.listener = listener;
-        }
-
-        @Override
-        public String toString() {
-            return "Label: " + label + " Text: " + text + " Focused: " + focused
-                    + " Visible: " + visible;
-        }
-
-        final class OneTimeLineWatcher implements TextWatcher {
-            private final CountDownLatch latch;
-            private final CharSequence expected;
-
-            OneTimeLineWatcher(CharSequence expectedValue) {
-                this.expected = expectedValue;
-                this.latch = new CountDownLatch(1);
-            }
-
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-            }
-
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                latch.countDown();
-            }
-
-            @Override
-            public void afterTextChanged(Editable s) {
-            }
-
-            void assertAutoFilled() throws Exception {
-                final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-                assertWithMessage("Timeout (%s ms) on Line %s", FILL_TIMEOUT.ms(), label)
-                        .that(set).isTrue();
-                final String actual = text.text.toString();
-                assertWithMessage("Wrong auto-fill value on Line %s", label)
-                        .that(actual).isEqualTo(expected.toString());
-            }
-        }
-    }
-
-    static final class Item {
-        private final Line line;
-        final int id;
-        private final String resourceId;
-        private CharSequence text;
-        private final boolean editable;
-        private final boolean sensitive;
-        private final String className;
-        private TextWatcher listener;
-
-        Item(Line line, int id, String resourceId, CharSequence text, boolean editable,
-                boolean sensitive) {
-            this.line = line;
-            this.id = id;
-            this.resourceId = resourceId;
-            this.text = text;
-            this.editable = editable;
-            this.sensitive = sensitive;
-            this.className = editable ? TEXT_CLASS : LABEL_CLASS;
-        }
-
-        AccessibilityNodeInfo provideAccessibilityNodeInfo(View parent, Context context) {
-            final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
-            node.setSource(parent, id);
-            node.setPackageName(context.getPackageName());
-            node.setClassName(className);
-            node.setEditable(editable);
-            node.setViewIdResourceName(resourceId);
-            node.setVisibleToUser(true);
-            node.setInputType(line.inputType);
-            if (line.absBounds != null) {
-                node.setBoundsInScreen(line.absBounds);
-            }
-            if (TextUtils.getTrimmedLength(text) > 0) {
-                // TODO: Must checked trimmed length because input fields use 8 empty spaces to
-                // set width
-                node.setText(text);
-            }
-            return node;
-        }
-
-        private void autofill(CharSequence value) {
-            if (!editable) {
-                Log.w(TAG, "Item for id " + id + " is not editable: " + this);
-                return;
-            }
-            text = value;
-            if (listener != null) {
-                Log.d(TAG, "Notify listener: " + text);
-                listener.onTextChanged(text, 0, 0, 0);
-            }
-        }
-
-        @Override
-        public String toString() {
-            return id + "/" + resourceId + ": " + text + (editable ? " (editable)" : " (read-only)"
-                    + (sensitive ? " (sensitive)" : " (sanitized"));
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VisibilitySetterActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/VisibilitySetterActionTest.java
deleted file mode 100644
index 202c5fd..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/VisibilitySetterActionTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.content.Context;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.VisibilitySetterAction;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class VisibilitySetterActionTest {
-
-    private static final Context sContext = getInstrumentation().getTargetContext();
-    private final ViewGroup mRootView = new ViewGroup(sContext) {
-
-        @Override
-        protected void onLayout(boolean changed, int l, int t, int r, int b) {}
-    };
-
-    @Test
-    public void testValidVisibilities() {
-        assertThat(new VisibilitySetterAction.Builder(42, View.VISIBLE).build()).isNotNull();
-        assertThat(new VisibilitySetterAction.Builder(42, View.GONE).build()).isNotNull();
-        assertThat(new VisibilitySetterAction.Builder(42, View.INVISIBLE).build()).isNotNull();
-    }
-
-    @Test
-    public void testInvalidVisibilities() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new VisibilitySetterAction.Builder(42, 666).build());
-        final VisibilitySetterAction.Builder validBuilder =
-                new VisibilitySetterAction.Builder(42, View.VISIBLE);
-        assertThrows(IllegalArgumentException.class,
-                () -> validBuilder.setVisibility(108, 666).build());
-    }
-
-    @Test
-    public void testOneChild() {
-        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
-                .build();
-        final View view = new View(sContext);
-        view.setId(42);
-        view.setVisibility(View.GONE);
-        mRootView.addView(view);
-
-        action.onClick(mRootView);
-
-        assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void testOneChildAddedTwice() {
-        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
-                .setVisibility(42, View.INVISIBLE)
-                .build();
-        final View view = new View(sContext);
-        view.setId(42);
-        view.setVisibility(View.GONE);
-        mRootView.addView(view);
-
-        action.onClick(mRootView);
-
-        assertThat(view.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    public void testMultipleChildren() {
-        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
-                .setVisibility(108, View.INVISIBLE)
-                .build();
-        final View view1 = new View(sContext);
-        view1.setId(42);
-        view1.setVisibility(View.GONE);
-        mRootView.addView(view1);
-
-        final View view2 = new View(sContext);
-        view2.setId(108);
-        view2.setVisibility(View.GONE);
-        mRootView.addView(view2);
-
-        action.onClick(mRootView);
-
-        assertThat(view1.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(view2.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    public void testNoMoreInteractionsAfterBuild() {
-        final VisibilitySetterAction.Builder builder =
-                new VisibilitySetterAction.Builder(42, View.VISIBLE);
-
-        assertThat(builder.build()).isNotNull();
-        assertThrows(IllegalStateException.class, () -> builder.build());
-        assertThrows(IllegalStateException.class, () -> builder.setVisibility(108, View.GONE));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Visitor.java b/tests/autofillservice/src/android/autofillservice/cts/Visitor.java
deleted file mode 100644
index 95bafa7..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/Visitor.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-/**
- * A generic visitor.
- *
- * <p>Typically used by activities under test to provide a way to run an action on the view using
- * the UI thread. Example:
- * <pre><code>
- * void onUsername(ViewVisitor<EditText> v) {
- *     runOnUiThread(() -> v.visit(mUsername));
- * }
- * </code></pre>
- */
-// TODO: move to common code
-public interface Visitor<T> {
-
-    void visit(T object);
-}
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
deleted file mode 100644
index 5946442..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.WEBVIEW_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.view.View;
-import android.webkit.WebResourceRequest;
-import android.webkit.WebResourceResponse;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class WebViewActivity extends AbstractWebViewActivity {
-
-    private static final String TAG = "WebViewActivity";
-    private static final String FAKE_URL = "https://" + FAKE_DOMAIN + ":666/login.html";
-    static final String ID_WEBVIEW = "webview";
-
-    static final String ID_OUTSIDE1 = "outside1";
-    static final String ID_OUTSIDE2 = "outside2";
-
-    private LinearLayout mParent;
-    private LinearLayout mOutsideContainer1;
-    private LinearLayout mOutsideContainer2;
-    EditText mOutside1;
-    EditText mOutside2;
-
-    private UiObject2 mUsernameLabel;
-    private UiObject2 mUsernameInput;
-    private UiObject2 mPasswordLabel;
-    private UiObject2 mPasswordInput;
-    private UiObject2 mLoginButton;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.webview_activity);
-
-        mParent = findViewById(R.id.parent);
-        mOutsideContainer1 = findViewById(R.id.outsideContainer1);
-        mOutsideContainer2 = findViewById(R.id.outsideContainer2);
-        mOutside1 = findViewById(R.id.outside1);
-        mOutside2 = findViewById(R.id.outside2);
-    }
-
-    public MyWebView loadWebView(UiBot uiBot) throws Exception {
-        return loadWebView(uiBot, false);
-    }
-
-    public MyWebView loadWebView(UiBot uiBot, boolean usingAppContext) throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> {
-            final Context context = usingAppContext ? getApplicationContext() : this;
-            mWebView = new MyWebView(context);
-            mParent.addView(mWebView);
-            mWebView.setWebViewClient(new WebViewClient() {
-                // WebView does not set the WebDomain on file:// requests, so we need to use an
-                // https:// request and intercept it to provide the real data.
-                @Override
-                public WebResourceResponse shouldInterceptRequest(WebView view,
-                        WebResourceRequest request) {
-                    final String url = request.getUrl().toString();
-                    if (!url.equals(FAKE_URL)) {
-                        Log.d(TAG, "Ignoring " + url);
-                        return super.shouldInterceptRequest(view, request);
-                    }
-
-                    final String rawPath = request.getUrl().getPath()
-                            .substring(1); // Remove leading /
-                    Log.d(TAG, "Converting " + url + " to " + rawPath);
-                    // NOTE: cannot use try-with-resources because it would close the stream before
-                    // WebView uses it.
-                    try {
-                        return new WebResourceResponse("text/html", "utf-8",
-                                getAssets().open(rawPath));
-                    } catch (IOException e) {
-                        throw new IllegalArgumentException("Error opening " + rawPath, e);
-                    }
-                }
-
-                @Override
-                public void onPageFinished(WebView view, String url) {
-                    Log.v(TAG, "onPageFinished(): " + url);
-                    latch.countDown();
-                }
-            });
-            mWebView.loadUrl(FAKE_URL);
-        });
-
-        // Wait until it's loaded.
-        if (!latch.await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
-            throw new RetryableException(WEBVIEW_TIMEOUT, "WebView not loaded");
-        }
-
-        // Validation check to make sure autofill was enabled when the WebView was created
-        assertThat(mWebView.isAutofillEnabled()).isTrue();
-
-        // WebView builds its accessibility tree asynchronously and only after being queried the
-        // first time, so we should first find the WebView and query some of its properties,
-        // wait for its accessibility tree to be populated (by blocking until a known element
-        // appears), then cache the objects for further use.
-
-        // NOTE: we cannot search by resourceId because WebView does not set them...
-
-        // Wait for known element...
-        mUsernameLabel = uiBot.assertShownByText("Username: ", WEBVIEW_TIMEOUT);
-        // ...then cache the others
-        mUsernameInput = getInput(uiBot, mUsernameLabel);
-        mPasswordLabel = uiBot.findRightAwayByText("Password: ");
-        mPasswordInput = getInput(uiBot, mPasswordLabel);
-        mLoginButton = uiBot.findRightAwayByText("Login");
-
-        return mWebView;
-    }
-
-    public void loadOutsideViews() {
-        syncRunOnUiThread(() -> {
-            mOutsideContainer1.setVisibility(View.VISIBLE);
-            mOutsideContainer2.setVisibility(View.VISIBLE);
-        });
-    }
-
-    public UiObject2 getUsernameLabel() throws Exception {
-        return mUsernameLabel;
-    }
-
-    public UiObject2 getPasswordLabel() throws Exception {
-        return mPasswordLabel;
-    }
-
-    public UiObject2 getUsernameInput() throws Exception {
-        return mUsernameInput;
-    }
-
-    public UiObject2 getPasswordInput() throws Exception {
-        return mPasswordInput;
-    }
-
-    public UiObject2 getLoginButton() throws Exception {
-        return mLoginButton;
-    }
-
-    @Override
-    public void clearFocus() {
-        syncRunOnUiThread(() -> mParent.requestFocus());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
deleted file mode 100644
index bd3682f..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_PASSWORD;
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_USERNAME;
-import static android.autofillservice.cts.WebViewActivity.ID_OUTSIDE1;
-import static android.autofillservice.cts.WebViewActivity.ID_OUTSIDE2;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.platform.test.annotations.AppModeFull;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.ViewStructure.HtmlInfo;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-public class WebViewActivityTest extends AbstractWebViewTestCase<WebViewActivity> {
-
-    private static final String TAG = "WebViewActivityTest";
-
-    private WebViewActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<WebViewActivity> getActivityRule() {
-        return new AutofillActivityTestRule<WebViewActivity>(WebViewActivity.class) {
-
-            // TODO(b/111838239): latest WebView implementation calls AutofillManager.isEnabled() to
-            // disable autofill for optimization when it returns false, and unfortunately the value
-            // returned by that method does not change when the service is enabled / disabled, so we
-            // need to start enable the service before launching the activity.
-            // Once that's fixed, remove this overridden method.
-            @Override
-            protected void beforeActivityLaunched() {
-                super.beforeActivityLaunched();
-                Log.i(TAG, "Setting service before launching the activity");
-                enableService();
-            }
-
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillOneDataset() is enough")
-    public void testAutofillNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        mActivity.loadWebView(mUiBot);
-
-        // Set expectations.
-        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        sReplier.getNextFillRequest();
-
-        // Assert not shown.
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    @Test
-    public void testAutofillOneDataset() throws Exception {
-        autofillOneDatasetTest(false);
-    }
-
-    @Ignore("blocked on b/74793485")
-    @Test
-    @AppModeFull(reason = "testAutofillOneDataset() is enough")
-    public void testAutofillOneDataset_usingAppContext() throws Exception {
-        autofillOneDatasetTest(true);
-    }
-
-    private void autofillOneDatasetTest(boolean usesAppContext) throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot, usesAppContext);
-        // Validation check to make sure autofill is enabled in the application context
-        Helper.assertAutofillEnabled(myWebView.getContext(), true);
-
-        // Set expectations.
-        myWebView.expectAutofill("dude", "sweet");
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(HTML_NAME_USERNAME, "dude")
-                .setField(HTML_NAME_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-
-        // Change focus around.
-        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-        mActivity.getUsernameLabel().click();
-        callback.assertUiHiddenEvent(myWebView, usernameChildId);
-        mUiBot.assertNoDatasets();
-        mActivity.getPasswordInput().click();
-        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
-
-        // Now Autofill it.
-        mUiBot.selectDataset(datasetPicker, "The Dude");
-        myWebView.assertAutofilled();
-        mUiBot.assertNoDatasets();
-        callback.assertUiHiddenEvent(myWebView, passwordChildId);
-
-        // Assert structure passed to service.
-        try {
-            final ViewNode webViewNode =
-                    Helper.findWebViewNodeByFormName(fillRequest.structure, "FORM AM I");
-            assertThat(webViewNode.getClassName()).isEqualTo("android.webkit.WebView");
-            assertThat(webViewNode.getWebDomain()).isEqualTo(WebViewActivity.FAKE_DOMAIN);
-            assertThat(webViewNode.getWebScheme()).isEqualTo("https");
-
-            final ViewNode usernameNode =
-                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_USERNAME);
-            Helper.assertTextIsSanitized(usernameNode);
-            final HtmlInfo usernameHtmlInfo = Helper.assertHasHtmlTag(usernameNode, "input");
-            Helper.assertHasAttribute(usernameHtmlInfo, "type", "text");
-            Helper.assertHasAttribute(usernameHtmlInfo, "name", "username");
-            assertThat(usernameNode.isFocused()).isTrue();
-            assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
-            assertThat(usernameNode.getHint()).isEqualTo("There's no place like a holder");
-
-            final ViewNode passwordNode =
-                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_PASSWORD);
-            Helper.assertTextIsSanitized(passwordNode);
-            final HtmlInfo passwordHtmlInfo = Helper.assertHasHtmlTag(passwordNode, "input");
-            Helper.assertHasAttribute(passwordHtmlInfo, "type", "password");
-            Helper.assertHasAttribute(passwordHtmlInfo, "name", "password");
-            assertThat(passwordNode.getAutofillHints()).asList()
-                    .containsExactly("current-password");
-            assertThat(passwordNode.getHint()).isEqualTo("Holder it like it cannnot passer a word");
-            assertThat(passwordNode.isFocused()).isFalse();
-        } catch (RuntimeException | Error e) {
-            Helper.dumpStructure("failed on testAutofillOneDataset()", fillRequest.structure);
-            throw e;
-        }
-    }
-
-    @Test
-    public void testSaveOnly() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        mActivity.loadWebView(mUiBot);
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
-                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
-                .build());
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        sReplier.getNextFillRequest();
-
-        // Assert not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
-        } else {
-            mActivity.getUsernameInput().setText("DUDE");
-            mActivity.getPasswordInput().setText("SWEET");
-        }
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode usernameNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_USERNAME);
-        final ViewNode passwordNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_PASSWORD);
-        if (INJECT_EVENTS) {
-            Helper.assertTextAndValue(usernameNode, "u");
-            Helper.assertTextAndValue(passwordNode, "p");
-        } else {
-            Helper.assertTextAndValue(usernameNode, "DUDE");
-            Helper.assertTextAndValue(passwordNode, "SWEET");
-        }
-    }
-
-    @Test
-    public void testAutofillAndSave() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
-
-        // Set expectations.
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        myWebView.expectAutofill("dude", "sweet");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
-                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(HTML_NAME_USERNAME, "dude")
-                        .setField(HTML_NAME_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-
-        // Assert structure passed to service.
-        final ViewNode usernameNode = Helper.findNodeByHtmlName(fillRequest.structure,
-                HTML_NAME_USERNAME);
-        Helper.assertTextIsSanitized(usernameNode);
-        assertThat(usernameNode.isFocused()).isTrue();
-        assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
-        final ViewNode passwordNode = Helper.findNodeByHtmlName(fillRequest.structure,
-                HTML_NAME_PASSWORD);
-        Helper.assertTextIsSanitized(passwordNode);
-        assertThat(passwordNode.getAutofillHints()).asList().containsExactly("current-password");
-        assertThat(passwordNode.isFocused()).isFalse();
-
-        // Autofill it.
-        mUiBot.selectDataset("The Dude");
-        myWebView.assertAutofilled();
-        callback.assertUiHiddenEvent(myWebView, usernameChildId);
-
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
-        } else {
-            mActivity.getUsernameInput().setText("DUDE");
-            mActivity.getPasswordInput().setText("SWEET");
-        }
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode usernameNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_USERNAME);
-        final ViewNode passwordNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_PASSWORD);
-        if (INJECT_EVENTS) {
-            Helper.assertTextAndValue(usernameNode2, "dudeu");
-            Helper.assertTextAndValue(passwordNode2, "sweetp");
-        } else {
-            Helper.assertTextAndValue(usernameNode2, "DUDE");
-            Helper.assertTextAndValue(passwordNode2, "SWEET");
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillAndSave() is enough")
-    public void testAutofillAndSave_withExternalViews_loadWebViewFirst() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load views
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
-        mActivity.loadOutsideViews();
-
-        // Set expectations.
-        myWebView.expectAutofill("dude", "sweet");
-        final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
-                mActivity.mOutside1, "duder");
-        final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
-                mActivity.mOutside2, "sweeter");
-        mActivity.mOutside1.addTextChangedListener(outside1Watcher);
-        mActivity.mOutside2.addTextChangedListener(outside2Watcher);
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
-                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
-                        .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
-                        .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
-                        .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("USER");
-        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-
-        // Assert structure passed to service.
-        final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
-                HTML_NAME_USERNAME);
-        Helper.assertTextIsSanitized(usernameFillNode);
-        assertThat(usernameFillNode.isFocused()).isTrue();
-        assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
-        final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
-                HTML_NAME_PASSWORD);
-        Helper.assertTextIsSanitized(passwordFillNode);
-        assertThat(passwordFillNode.getAutofillHints()).asList()
-                .containsExactly("current-password");
-        assertThat(passwordFillNode.isFocused()).isFalse();
-
-        final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest.structure,
-                ID_OUTSIDE1);
-        Helper.assertTextIsSanitized(outside1FillNode);
-        final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest.structure,
-                ID_OUTSIDE2);
-        Helper.assertTextIsSanitized(outside2FillNode);
-
-        // Move focus around to make sure UI is shown accordingly
-        mActivity.clearFocus();
-        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
-        callback.assertUiHiddenEvent(myWebView, usernameChildId);
-        mUiBot.assertDatasets("OUT1");
-        callback.assertUiShownEvent(mActivity.mOutside1);
-
-        mActivity.clearFocus();
-        mActivity.getPasswordInput().click();
-        callback.assertUiHiddenEvent(mActivity.mOutside1);
-        mUiBot.assertDatasets("PASS");
-        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-
-        mActivity.clearFocus();
-        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
-        callback.assertUiHiddenEvent(myWebView, passwordChildId);
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
-        callback.assertUiShownEvent(mActivity.mOutside2);
-
-        // Autofill it.
-        mUiBot.selectDataset(datasetPicker, "OUT2");
-        callback.assertUiHiddenEvent(mActivity.mOutside2);
-
-        myWebView.assertAutofilled();
-        outside1Watcher.assertAutoFilled();
-        outside2Watcher.assertAutoFilled();
-
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
-        } else {
-            mActivity.getUsernameInput().setText("DUDE");
-            mActivity.getPasswordInput().setText("SWEET");
-        }
-        mActivity.runOnUiThread(() -> {
-            mActivity.mOutside1.setText("DUDER");
-            mActivity.mOutside2.setText("SWEETER");
-        });
-
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_USERNAME);
-        final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_PASSWORD);
-        if (INJECT_EVENTS) {
-            Helper.assertTextAndValue(usernameSaveNode, "dudeu");
-            Helper.assertTextAndValue(passwordSaveNode, "sweetp");
-        } else {
-            Helper.assertTextAndValue(usernameSaveNode, "DUDE");
-            Helper.assertTextAndValue(passwordSaveNode, "SWEET");
-        }
-
-        final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
-                ID_OUTSIDE1);
-        Helper.assertTextAndValue(outside1SaveNode, "DUDER");
-        final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
-                ID_OUTSIDE2);
-        Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
-    }
-
-
-    @Test
-    @Ignore("blocked on b/69461853")
-    @AppModeFull(reason = "testAutofillAndSave() is enough")
-    public void testAutofillAndSave_withExternalViews_loadExternalViewsFirst() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load outside views
-        mActivity.loadOutsideViews();
-
-        // Set expectations.
-        final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
-                mActivity.mOutside1, "duder");
-        final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
-                mActivity.mOutside2, "sweeter");
-        mActivity.mOutside1.addTextChangedListener(outside1Watcher);
-        mActivity.mOutside2.addTextChangedListener(outside2Watcher);
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        sReplier.setIdMode(IdMode.RESOURCE_ID);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
-                        .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("OUT1");
-        callback.assertUiShownEvent(mActivity.mOutside1);
-
-        // Move focus around to make sure UI is shown accordingly
-        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
-        callback.assertUiHiddenEvent(mActivity.mOutside1);
-        mUiBot.assertDatasets("OUT2");
-        callback.assertUiShownEvent(mActivity.mOutside2);
-
-        // Assert structure passed to service.
-        final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
-                ID_OUTSIDE1);
-        Helper.assertTextIsSanitized(outside1FillNode);
-        final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
-                ID_OUTSIDE2);
-        Helper.assertTextIsSanitized(outside2FillNode);
-
-        // Now load Webiew
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
-
-        // Set expectations
-        myWebView.expectAutofill("dude", "sweet");
-        sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
-                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
-                        .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        callback.assertUiHiddenEvent(mActivity.mOutside2);
-        mUiBot.assertDatasets("USER");
-        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-
-        // Move focus around to make sure UI is shown accordingly
-        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
-        callback.assertUiHiddenEvent(myWebView, usernameChildId);
-        mUiBot.assertDatasets("OUT1");
-        callback.assertUiShownEvent(mActivity.mOutside1);
-
-        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
-        callback.assertUiHiddenEvent(mActivity.mOutside1);
-        mUiBot.assertDatasets("OUT2");
-        callback.assertUiShownEvent(mActivity.mOutside2);
-
-        mActivity.getPasswordInput().click();
-        callback.assertUiHiddenEvent(mActivity.mOutside2);
-        mUiBot.assertDatasets("PASS");
-        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-
-        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
-        callback.assertUiHiddenEvent(myWebView, passwordChildId);
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
-        callback.assertUiShownEvent(mActivity.mOutside2);
-
-        // Assert structure passed to service.
-        final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
-                HTML_NAME_USERNAME);
-        Helper.assertTextIsSanitized(usernameFillNode);
-        assertThat(usernameFillNode.isFocused()).isTrue();
-        assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
-        final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
-                HTML_NAME_PASSWORD);
-        Helper.assertTextIsSanitized(passwordFillNode);
-        assertThat(passwordFillNode.getAutofillHints()).asList()
-                .containsExactly("current-password");
-        assertThat(passwordFillNode.isFocused()).isFalse();
-
-        // Autofill external views (2nd partition)
-        mUiBot.selectDataset(datasetPicker, "OUT2");
-        callback.assertUiHiddenEvent(mActivity.mOutside2);
-        outside1Watcher.assertAutoFilled();
-        outside2Watcher.assertAutoFilled();
-
-        // Autofill Webview (1st partition)
-        mActivity.getUsernameInput().click();
-        callback.assertUiShownEventForVirtualChild(myWebView);
-        mUiBot.selectDataset("USER");
-        myWebView.assertAutofilled();
-
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
-        } else {
-            mActivity.getUsernameInput().setText("DUDE");
-            mActivity.getPasswordInput().setText("SWEET");
-        }
-        mActivity.runOnUiThread(() -> {
-            mActivity.mOutside1.setText("DUDER");
-            mActivity.mOutside2.setText("SWEETER");
-        });
-
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_USERNAME);
-        final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_PASSWORD);
-        if (INJECT_EVENTS) {
-            Helper.assertTextAndValue(usernameSaveNode, "dudeu");
-            Helper.assertTextAndValue(passwordSaveNode, "sweetp");
-        } else {
-            Helper.assertTextAndValue(usernameSaveNode, "DUDE");
-            Helper.assertTextAndValue(passwordSaveNode, "SWEET");
-        }
-
-        final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
-                ID_OUTSIDE1);
-        Helper.assertTextAndValue(outside1SaveNode, "DUDER");
-        final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
-                ID_OUTSIDE2);
-        Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivity.java
deleted file mode 100644
index 2e3e74c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivity.java
+++ /dev/null
@@ -1,173 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.WEBVIEW_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.Bundle;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.webkit.WebResourceRequest;
-import android.webkit.WebResourceResponse;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class WebViewMultiScreenLoginActivity extends AbstractWebViewActivity {
-
-    private static final String TAG = "WebViewMultiScreenLoginActivity";
-    private static final String FAKE_USERNAME_URL = "https://" + FAKE_DOMAIN + ":666/username.html";
-    private static final String FAKE_PASSWORD_URL = "https://" + FAKE_DOMAIN + ":666/password.html";
-
-    private UiObject2 mUsernameLabel;
-    private UiObject2 mUsernameInput;
-    private UiObject2 mNextButton;
-
-    private UiObject2 mPasswordLabel;
-    private UiObject2 mPasswordInput;
-    private UiObject2 mLoginButton;
-
-    private final Map<String, CountDownLatch> mLatches = new HashMap<>();
-
-    public WebViewMultiScreenLoginActivity() {
-        mLatches.put(FAKE_USERNAME_URL, new CountDownLatch(1));
-        mLatches.put(FAKE_PASSWORD_URL, new CountDownLatch(1));
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.webview_only_activity);
-        mWebView = findViewById(R.id.my_webview);
-    }
-
-    public MyWebView loadWebView(UiBot uiBot) throws Exception {
-        syncRunOnUiThread(() -> {
-            mWebView.setWebViewClient(new WebViewClient() {
-                // WebView does not set the WebDomain on file:// requests, so we need to use an
-                // https:// request and intercept it to provide the real data.
-                @Override
-                public WebResourceResponse shouldInterceptRequest(WebView view,
-                        WebResourceRequest request) {
-                    final String url = request.getUrl().toString();
-                    if (!url.equals(FAKE_USERNAME_URL) && !url.equals(FAKE_PASSWORD_URL)) {
-                        Log.d(TAG, "Ignoring " + url);
-                        return super.shouldInterceptRequest(view, request);
-                    }
-
-                    final String rawPath = request.getUrl().getPath()
-                            .substring(1); // Remove leading /
-                    Log.d(TAG, "Converting " + url + " to " + rawPath);
-                    // NOTE: cannot use try-with-resources because it would close the stream before
-                    // WebView uses it.
-                    try {
-                        return new WebResourceResponse("text/html", "utf-8",
-                                getAssets().open(rawPath));
-                    } catch (IOException e) {
-                        throw new IllegalArgumentException("Error opening " + rawPath, e);
-                    }
-                }
-
-                @Override
-                public void onPageFinished(WebView view, String url) {
-                    final CountDownLatch latch = mLatches.get(url);
-                    Log.v(TAG, "onPageFinished(): " + url + " latch: " + latch);
-                    if (latch != null) {
-                        latch.countDown();
-                    }
-                }
-            });
-            mWebView.loadUrl(FAKE_USERNAME_URL);
-        });
-
-        // Wait until it's loaded.
-        if (!mLatches.get(FAKE_USERNAME_URL).await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
-            throw new RetryableException(WEBVIEW_TIMEOUT, "WebView not loaded");
-        }
-
-        // Validation check to make sure autofill was enabled when the WebView was created
-        assertThat(mWebView.isAutofillEnabled()).isTrue();
-
-        // WebView builds its accessibility tree asynchronously and only after being queried the
-        // first time, so we should first find the WebView and query some of its properties,
-        // wait for its accessibility tree to be populated (by blocking until a known element
-        // appears), then cache the objects for further use.
-
-        // NOTE: we cannot search by resourceId because WebView does not set them...
-
-        // Wait for known element...
-        mUsernameLabel = uiBot.assertShownByText("Username: ", WEBVIEW_TIMEOUT);
-        // ...then cache the others
-        mUsernameInput = getInput(uiBot, mUsernameLabel);
-        mNextButton = uiBot.findRightAwayByText("Next");
-
-        return mWebView;
-    }
-
-    void waitForPasswordScreen(UiBot uiBot) throws Exception {
-        // Wait until it's loaded.
-        if (!mLatches.get(FAKE_PASSWORD_URL).await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
-            throw new RetryableException(WEBVIEW_TIMEOUT, "Password page not loaded");
-        }
-
-        // WebView builds its accessibility tree asynchronously and only after being queried the
-        // first time, so we should first find the WebView and query some of its properties,
-        // wait for its accessibility tree to be populated (by blocking until a known element
-        // appears), then cache the objects for further use.
-
-        // NOTE: we cannot search by resourceId because WebView does not set them...
-
-        // Wait for known element...
-        mPasswordLabel = uiBot.assertShownByText("Password: ", WEBVIEW_TIMEOUT);
-        // ...then cache the others
-        mPasswordInput = getInput(uiBot, mPasswordLabel);
-        mLoginButton = uiBot.findRightAwayByText("Login");
-    }
-
-    public UiObject2 getUsernameLabel() throws Exception {
-        return mUsernameLabel;
-    }
-
-    public UiObject2 getUsernameInput() throws Exception {
-        return mUsernameInput;
-    }
-
-    public UiObject2 getNextButton() throws Exception {
-        return mNextButton;
-    }
-
-    public UiObject2 getPasswordLabel() throws Exception {
-        return mPasswordLabel;
-    }
-
-    public UiObject2 getPasswordInput() throws Exception {
-        return mPasswordInput;
-    }
-
-    public UiObject2 getLoginButton() throws Exception {
-        return mLoginButton;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivityTest.java
deleted file mode 100644
index f2071d3..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivityTest.java
+++ /dev/null
@@ -1,304 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.AbstractWebViewActivity.HTML_NAME_PASSWORD;
-import static android.autofillservice.cts.AbstractWebViewActivity.HTML_NAME_USERNAME;
-import static android.autofillservice.cts.CustomDescriptionHelper.newCustomDescriptionWithUsernameAndPassword;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByHtmlName;
-import static android.autofillservice.cts.Helper.getAutofillId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.ComponentName;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.autofill.AutofillId;
-
-import org.junit.Test;
-
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Pattern;
-
-public class WebViewMultiScreenLoginActivityTest
-        extends AbstractWebViewTestCase<WebViewMultiScreenLoginActivity> {
-
-    private static final String TAG = "WebViewMultiScreenLoginTest";
-
-    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
-
-    private WebViewMultiScreenLoginActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<WebViewMultiScreenLoginActivity> getActivityRule() {
-        return new AutofillActivityTestRule<WebViewMultiScreenLoginActivity>(
-                WebViewMultiScreenLoginActivity.class) {
-
-            // TODO(b/111838239): latest WebView implementation calls AutofillManager.isEnabled() to
-            // disable autofill for optimization when it returns false, and unfortunately the value
-            // returned by that method does not change when the service is enabled / disabled, so we
-            // need to start enable the service before launching the activity.
-            // Once that's fixed, remove this overridden method.
-            @Override
-            protected void beforeActivityLaunched() {
-                super.beforeActivityLaunched();
-                Log.i(TAG, "Setting service before launching the activity");
-                enableService();
-            }
-
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testSave_eachFieldSeparately() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
-        // Validation check to make sure autofill is enabled in the application context
-        Helper.assertAutofillEnabled(myWebView.getContext(), true);
-
-        /*
-         * First screen: username
-         */
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, HTML_NAME_USERNAME)
-                .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    final AutofillId usernameId = getAutofillId(nodeResolver, HTML_NAME_USERNAME);
-                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
-                            .Builder(usernameId, MATCH_ALL, "$1").build();
-                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
-                            .addChild(R.id.username, usernameTrans)
-                            .build());
-                })
-                .build());
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts).hasSize(1);
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-        } else {
-            mActivity.getUsernameInput().setText("dude");
-        }
-        mActivity.getNextButton().click();
-
-        // Assert UI
-        final UiObject2 saveUi1 = mUiBot.assertSaveShowing(
-                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME);
-
-        mUiBot.assertChildText(saveUi1, ID_USERNAME_LABEL, "User:");
-        mUiBot.assertChildText(saveUi1, ID_USERNAME, "dude");
-
-        // Assert save request
-        mUiBot.saveForAutofill(saveUi1, true);
-        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
-        assertThat(saveRequest1.contexts).hasSize(1);
-        assertTextAndValue(findNodeByHtmlName(saveRequest1.structure, HTML_NAME_USERNAME), "dude");
-
-        /*
-         * Second screen: password
-         */
-
-        mActivity.waitForPasswordScreen(mUiBot);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, HTML_NAME_PASSWORD)
-                .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
-                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
-                            .Builder(passwordId, MATCH_ALL, "$1").build();
-                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
-                            .addChild(R.id.password, passwordTrans)
-                            .build());
-                })
-                .build());
-        // Trigger autofill.
-        mActivity.getPasswordInput().click();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts).hasSize(1);
-        mUiBot.assertNoDatasetsEver();
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
-        } else {
-            mActivity.getPasswordInput().setText("sweet");
-        }
-
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(
-                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_PASSWORD);
-        mUiBot.assertChildText(saveUi2, ID_PASSWORD_LABEL, "Pass:");
-        mUiBot.assertChildText(saveUi2, ID_PASSWORD, "sweet");
-
-        // Assert save request
-        mUiBot.saveForAutofill(saveUi2, true);
-        final SaveRequest saveRequest2 = sReplier.getNextSaveRequest();
-        assertThat(saveRequest2.contexts).hasSize(1);
-        assertTextAndValue(findNodeByHtmlName(saveRequest2.structure, HTML_NAME_PASSWORD), "sweet");
-    }
-
-    @Test
-    public void testSave_bothFieldsAtOnce() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
-        // Validation check to make sure autofill is enabled in the application context
-        Helper.assertAutofillEnabled(myWebView.getContext(), true);
-
-        /*
-         * First screen: username
-         */
-        final AtomicReference<AutofillId> usernameId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setIgnoreFields(HTML_NAME_USERNAME)
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
-                .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    usernameId.set(getAutofillId(nodeResolver, HTML_NAME_USERNAME));
-
-                })
-                .build());
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts).hasSize(1);
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Change username
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-        } else {
-            mActivity.getUsernameInput().setText("dude");
-        }
-
-        mActivity.getNextButton().click();
-
-        // Assert UI
-        mUiBot.assertSaveNotShowing();
-
-        /*
-         * Second screen: password
-         */
-
-        mActivity.waitForPasswordScreen(mUiBot);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                        HTML_NAME_PASSWORD)
-                .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
-                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
-                            .Builder(usernameId.get(), MATCH_ALL, "$1").build();
-                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
-                            .Builder(passwordId, MATCH_ALL, "$1").build();
-                    Log.d(TAG, "setting CustomDescription: u=" + usernameId + ", p=" + passwordId);
-                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
-                            .addChild(R.id.username, usernameTrans)
-                            .addChild(R.id.password, passwordTrans)
-                            .build());
-                })
-                .build());
-        // Trigger autofill.
-        mActivity.getPasswordInput().click();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts).hasSize(2);
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
-        } else {
-            mActivity.getPasswordInput().setText("sweet");
-        }
-
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(
-                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME,
-                SAVE_DATA_TYPE_PASSWORD);
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
-        mUiBot.assertChildText(saveUi, ID_PASSWORD, "sweet");
-        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
-        mUiBot.assertChildText(saveUi, ID_USERNAME, "dude");
-
-        // Assert save request
-        mUiBot.saveForAutofill(saveUi, true);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Username is set in the 1st context
-        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
-        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
-        assertTextAndValue(findNodeByHtmlName(previousStructure, HTML_NAME_USERNAME), "dude");
-        final ComponentName componentPrevious = previousStructure.getActivityComponent();
-        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
-
-        // Password is set in the 2nd context
-        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
-        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
-        assertTextAndValue(findNodeByHtmlName(currentStructure, HTML_NAME_PASSWORD), "sweet");
-        final ComponentName componentCurrent = currentStructure.getActivityComponent();
-        assertThat(componentCurrent).isEqualTo(mActivity.getComponentName());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
deleted file mode 100644
index 9e11da9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.support.test.uiautomator.UiObject2;
-import android.text.TextUtils;
-import android.util.Log;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-/**
- * Activity that displays a "Welcome USER" message after login.
- */
-public class WelcomeActivity extends AbstractAutoFillActivity {
-
-    private static WelcomeActivity sInstance;
-
-    private static final String TAG = "WelcomeActivity";
-
-    static final String EXTRA_MESSAGE = "message";
-    static final String ID_WELCOME = "welcome";
-
-    private static int sPendingIntentId;
-    private static PendingIntent sPendingIntent;
-
-    private TextView mWelcome;
-
-    public WelcomeActivity() {
-        sInstance = this;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.welcome_activity);
-
-        mWelcome = (TextView) findViewById(R.id.welcome);
-
-        final Intent intent = getIntent();
-        final String message = intent.getStringExtra(EXTRA_MESSAGE);
-
-        if (!TextUtils.isEmpty(message)) {
-            mWelcome.setText(message);
-        }
-
-        Log.d(TAG, "Message: " + message);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        Log.v(TAG, "Setting sInstance to null onDestroy()");
-        sInstance = null;
-    }
-
-    @Override
-    public void finish() {
-        super.finish();
-        Log.d(TAG, "So long and thanks for all the finish!");
-
-        if (sPendingIntent != null) {
-            Log.v(TAG, " canceling pending intent on finish(): " + sPendingIntent);
-            sPendingIntent.cancel();
-        }
-    }
-
-    static void finishIt() {
-        if (sInstance != null) {
-            sInstance.finish();
-        }
-    }
-
-    // TODO: reuse in other places
-    static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
-        assertShowing(uiBot, null);
-    }
-
-    // TODO: reuse in other places
-    static void assertShowing(UiBot uiBot, @Nullable String expectedMessage) throws Exception {
-        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
-        if (expectedMessage == null) {
-            expectedMessage = "Welcome to the jungle!";
-        }
-        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
-                .isEqualTo(expectedMessage);
-    }
-
-    public static IntentSender createSender(Context context, String message) {
-        if (sPendingIntent != null) {
-            throw new IllegalArgumentException("Already have pending intent (id="
-                    + sPendingIntentId + "): " + sPendingIntent);
-        }
-        ++sPendingIntentId;
-        Log.v(TAG, "createSender: id=" + sPendingIntentId + " message=" + message);
-        final Intent intent = new Intent(context, WelcomeActivity.class)
-                .putExtra(EXTRA_MESSAGE, message)
-                .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
-        sPendingIntent = PendingIntent.getActivity(context, sPendingIntentId, intent, 0);
-        return sPendingIntent.getIntentSender();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WindowChangeTimeoutException.java b/tests/autofillservice/src/android/autofillservice/cts/WindowChangeTimeoutException.java
deleted file mode 100644
index 1225a47..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WindowChangeTimeoutException.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import androidx.annotation.NonNull;
-
-import com.android.compatibility.common.util.RetryableException;
-
-public final class WindowChangeTimeoutException extends RetryableException {
-
-    public WindowChangeTimeoutException(@NonNull Throwable cause, long timeoutMillis) {
-        super(cause, "no window change event in %dms", timeoutMillis);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java
new file mode 100644
index 0000000..0be23d4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.autofillservice.cts.testcore.AutofillTestWatcher;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.PixelCopy;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.SynchronousPixelCopy;
+import com.android.compatibility.common.util.Timeout;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+  * Base class for all activities in this test suite
+  */
+public abstract class AbstractAutoFillActivity extends Activity {
+
+    private final CountDownLatch mDestroyedLatch = new CountDownLatch(1);
+    protected final String mTag = getClass().getSimpleName();
+    private MyAutofillCallback mCallback;
+
+    /**
+     * Run an action in the UI thread, and blocks caller until the action is finished.
+     */
+    public final void syncRunOnUiThread(Runnable action) {
+        syncRunOnUiThread(action, Timeouts.UI_TIMEOUT.ms());
+    }
+
+    /**
+     * Run an action in the UI thread, and blocks caller until the action is finished or it times
+     * out.
+     */
+    public final void syncRunOnUiThread(Runnable action, long timeoutMs) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        runOnUiThread(() -> {
+            action.run();
+            latch.countDown();
+        });
+        try {
+            if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+                throw new RetryableException("action on UI thread timed out after %d ms",
+                        timeoutMs);
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Interrupted", e);
+        }
+    }
+
+    public AutofillManager getAutofillManager() {
+        return getSystemService(AutofillManager.class);
+    }
+
+    /**
+     * Takes a screenshot from the whole activity.
+     *
+     * <p><b>Note:</b> this screenshot only contains the contents of the activity, it doesn't
+     * include the autofill UIs; if you need to check that, please use
+     * {@link UiBot#takeScreenshot()} instead.
+     */
+    public Bitmap takeScreenshot() {
+        return takeScreenshot(findViewById(android.R.id.content).getRootView());
+    }
+
+    /**
+     * Takes a screenshot from the a view.
+     */
+    public Bitmap takeScreenshot(View view) {
+        final Rect srcRect = new Rect();
+        syncRunOnUiThread(() -> view.getGlobalVisibleRect(srcRect));
+        final Bitmap dest = Bitmap.createBitmap(
+                srcRect.width(), srcRect.height(), Bitmap.Config.ARGB_8888);
+
+        final SynchronousPixelCopy copy = new SynchronousPixelCopy();
+        final int copyResult = copy.request(getWindow(), srcRect, dest);
+        assertThat(copyResult).isEqualTo(PixelCopy.SUCCESS);
+
+        return dest;
+    }
+
+    /**
+     * Registers and returns a custom callback for autofill events.
+     *
+     * <p>Note: caller doesn't need to call {@link #unregisterCallback()}, it will be automatically
+     * unregistered on {@link #finish()}.
+     */
+    public MyAutofillCallback registerCallback() {
+        assertWithMessage("already registered").that(mCallback).isNull();
+        mCallback = new MyAutofillCallback();
+        getAutofillManager().registerCallback(mCallback);
+        return mCallback;
+    }
+
+    /**
+     * Unregister the callback from the {@link AutofillManager}.
+     *
+     * <p>This method just neeed to be called when a test case wants to explicitly test the behavior
+     * of the activity when the callback is unregistered.
+     */
+    public void unregisterCallback() {
+        assertWithMessage("not registered").that(mCallback).isNotNull();
+        unregisterNonNullCallback();
+    }
+
+    /**
+     * Waits until {@link #onDestroy()} is called.
+     */
+    public void waintUntilDestroyed(@NonNull Timeout timeout) throws InterruptedException {
+        if (!mDestroyedLatch.await(timeout.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(timeout, "activity %s not destroyed", this);
+        }
+    }
+
+    private void unregisterNonNullCallback() {
+        getAutofillManager().unregisterCallback(mCallback);
+        mCallback = null;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        AutofillTestWatcher.registerActivity("onCreate()", this);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        // Activitiy is typically unregistered at finish(), but we need to unregister here too
+        // for the cases where it's destroyed due to a config change (like device rotation).
+        AutofillTestWatcher.unregisterActivity("onDestroy()", this);
+        mDestroyedLatch.countDown();
+    }
+
+    @Override
+    public void finish() {
+        finishOnly();
+        AutofillTestWatcher.unregisterActivity("finish()", this);
+    }
+
+    /**
+     * Finishes the activity, without unregistering it from {@link AutofillTestWatcher}.
+     */
+    public void finishOnly() {
+        if (mCallback != null) {
+            unregisterNonNullCallback();
+        }
+        super.finish();
+    }
+
+    /**
+     * Clears focus from input fields.
+     */
+    public void clearFocus() {
+        throw new UnsupportedOperationException("Not implemented by " + getClass());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractDatePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractDatePickerActivity.java
new file mode 100644
index 0000000..09a386d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractDatePickerActivity.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.MultipleTimesTextWatcher;
+import android.autofillservice.cts.testcore.OneTimeDateListener;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.DatePicker;
+import android.widget.EditText;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base class for an activity that has the following fields:
+ *
+ * <ul>
+ *   <li>A DatePicker (id: date_picker)
+ *   <li>An EditText that is filled with the DatePicker when it changes (id: output)
+ *   <li>An OK button that finishes it and navigates to the {@link WelcomeActivity}
+ * </ul>
+ *
+ * <p>It's abstract because the sub-class must provide the view id, so it can support multiple
+ * UI types (like calendar and spinner).
+ */
+public abstract class AbstractDatePickerActivity extends AbstractAutoFillActivity {
+
+    private static final long OK_TIMEOUT_MS = 1000;
+
+    public static final String ID_DATE_PICKER = "date_picker";
+    public static final String ID_OUTPUT = "output";
+
+    private DatePicker mDatePicker;
+    private EditText mOutput;
+    private Button mOk;
+
+    private FillExpectation mExpectation;
+    private CountDownLatch mOkLatch;
+
+    protected abstract int getContentView();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(getContentView());
+
+        mDatePicker = (DatePicker) findViewById(R.id.date_picker);
+
+        mDatePicker.setOnDateChangedListener((v, y, m, d) -> updateOutputWithDate(y, m, d));
+
+        mOutput = (EditText) findViewById(R.id.output);
+        mOk = (Button) findViewById(R.id.ok);
+        mOk.setOnClickListener((v) -> ok());
+    }
+
+    public DatePicker getDatePicker() {
+        return mDatePicker;
+    }
+
+    private void updateOutputWithDate(int year, int month, int day) {
+        final String date = year + "/" + month + "/" + day;
+        mOutput.setText(date);
+    }
+
+    private void ok() {
+        final Intent intent = new Intent(this, WelcomeActivity.class);
+        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Good news everyone! The world didn't end!");
+        startActivity(intent);
+        if (mOkLatch != null) {
+            // Latch is not set when activity launched outside tests
+            mOkLatch.countDown();
+        }
+        finish();
+    }
+
+    /**
+     * Sets the expectation for an auto-fill request, so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(String output, int year, int month, int day) {
+        mExpectation = new FillExpectation(output, year, month, day);
+        mOutput.addTextChangedListener(mExpectation.outputWatcher);
+        mDatePicker.setOnDateChangedListener((v, y, m, d) -> {
+            updateOutputWithDate(y, m, d);
+            mExpectation.dateListener.onDateChanged(v, y, m, d);
+        });
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, int, int, int)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        mExpectation.outputWatcher.assertAutoFilled();
+        mExpectation.dateListener.assertAutoFilled();
+    }
+
+    /**
+     * Visits the {@code output} in the UiThread.
+     */
+    public void onOutput(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mOutput));
+    }
+
+    /**
+     * Sets the date in the {@link DatePicker}.
+     */
+    public void setDate(int year, int month, int day) {
+        syncRunOnUiThread(() -> mDatePicker.updateDate(year, month, day));
+    }
+
+    /**
+     * Taps the ok button in the UI thread.
+     */
+    public void tapOk() throws Exception {
+        mOkLatch = new CountDownLatch(1);
+        syncRunOnUiThread(() -> mOk.performClick());
+        boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) waiting for OK action", OK_TIMEOUT_MS)
+                .that(called).isTrue();
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final MultipleTimesTextWatcher outputWatcher;
+        private final OneTimeDateListener dateListener;
+
+        private FillExpectation(String output, int year, int month, int day) {
+            // Output is called twice: by the DateChangeListener and by auto-fill.
+            outputWatcher = new MultipleTimesTextWatcher("output", 2, mOutput, output);
+            dateListener = new OneTimeDateListener("datePicker", mDatePicker, year, month, day);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractTimePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractTimePickerActivity.java
new file mode 100644
index 0000000..7b2e8e2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractTimePickerActivity.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.MultipleTimesTextWatcher;
+import android.autofillservice.cts.testcore.MultipleTimesTimeListener;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TimePicker;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base class for an activity that has the following fields:
+ *
+ * <ul>
+ *   <li>A TimePicker (id: date_picker)
+ *   <li>An EditText that is filled with the TimePicker when it changes (id: output)
+ *   <li>An OK button that finishes it and navigates to the {@link WelcomeActivity}
+ * </ul>
+ *
+ * <p>It's abstract because the sub-class must provide the view id, so it can support multiple
+ * UI types (like clock and spinner).
+ */
+public abstract class AbstractTimePickerActivity extends AbstractAutoFillActivity {
+
+    private static final long OK_TIMEOUT_MS = 1000;
+
+    public static final String ID_TIME_PICKER = "time_picker";
+    public static final String ID_OUTPUT = "output";
+
+    private TimePicker mTimePicker;
+    private EditText mOutput;
+    private Button mOk;
+
+    private FillExpectation mExpectation;
+    private CountDownLatch mOkLatch;
+
+    protected abstract int getContentView();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(getContentView());
+
+        mTimePicker = (TimePicker) findViewById(R.id.time_picker);
+
+        mTimePicker.setOnTimeChangedListener((v, m, h) -> updateOutputWithTime(m, h));
+
+        mOutput = (EditText) findViewById(R.id.output);
+        mOk = (Button) findViewById(R.id.ok);
+        mOk.setOnClickListener((v) -> ok());
+    }
+
+    private void updateOutputWithTime(int hour, int minute) {
+        final String time = hour + ":" + minute;
+        mOutput.setText(time);
+    }
+
+    private void ok() {
+        final Intent intent = new Intent(this, WelcomeActivity.class);
+        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "It's Adventure Time!");
+        startActivity(intent);
+        if (mOkLatch != null) {
+            // Latch is not set when activity launched outside tests
+            mOkLatch.countDown();
+        }
+        finish();
+    }
+
+    /**
+     * Sets the expectation for an auto-fill request, so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(String output, int hour, int minute) {
+        mExpectation = new FillExpectation(output, hour, minute);
+        mOutput.addTextChangedListener(mExpectation.outputWatcher);
+        mTimePicker.setOnTimeChangedListener((v, h, m) -> {
+            updateOutputWithTime(h, m);
+            mExpectation.timeListener.onTimeChanged(v, h, m);
+        });
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, int, int)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        mExpectation.timeListener.assertAutoFilled();
+        mExpectation.outputWatcher.assertAutoFilled();
+    }
+
+    /**
+     * Visits the {@code output} in the UiThread.
+     */
+    public void onOutput(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mOutput));
+    }
+
+    /**
+     * Sets the time in the {@link TimePicker}.
+     */
+    public void setTime(int hour, int minute) {
+        syncRunOnUiThread(() -> {
+            mTimePicker.setHour(hour);
+            mTimePicker.setMinute(minute);
+        });
+    }
+
+    /**
+     * Taps the ok button in the UI thread.
+     */
+    public void tapOk() throws Exception {
+        mOkLatch = new CountDownLatch(1);
+        syncRunOnUiThread(() -> mOk.performClick());
+        boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) waiting for OK action", OK_TIMEOUT_MS)
+                .that(called).isTrue();
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final MultipleTimesTextWatcher outputWatcher;
+        private final MultipleTimesTimeListener timeListener;
+
+        private FillExpectation(String output, int hour, int minute) {
+            // Output is called twice: by the TimeChangeListener and by auto-fill.
+            outputWatcher = new MultipleTimesTextWatcher("output", 2, mOutput, output);
+            timeListener = new MultipleTimesTimeListener("timePicker", 1, mTimePicker, hour,
+                    minute);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractWebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractWebViewActivity.java
new file mode 100644
index 0000000..a6b5938
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractWebViewActivity.java
@@ -0,0 +1,61 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import android.autofillservice.cts.testcore.UiBot;
+import android.os.SystemClock;
+import android.support.test.uiautomator.UiObject2;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+public abstract class AbstractWebViewActivity extends AbstractAutoFillActivity {
+
+    public static final String FAKE_DOMAIN = "y.u.no.real.server";
+
+    public static final String HTML_NAME_USERNAME = "username";
+    public static final String HTML_NAME_PASSWORD = "password";
+
+    protected MyWebView mWebView;
+
+    protected UiObject2 getInput(UiBot uiBot, UiObject2 label) throws Exception {
+        // Then the input is next.
+        final UiObject2 parent = label.getParent();
+        UiObject2 previous = null;
+        for (UiObject2 child : parent.getChildren()) {
+            if (label.equals(previous)) {
+                if (child.getClassName().equals(EditText.class.getName())) {
+                    return child;
+                }
+                uiBot.dumpScreen("getInput() for " + child + "failed");
+                throw new IllegalStateException("Invalid class for " + child);
+            }
+            previous = child;
+        }
+        uiBot.dumpScreen("getInput() for label " + label + "failed");
+        throw new IllegalStateException("could not find username (label=" + label + ")");
+    }
+
+    public void dispatchKeyPress(int keyCode) {
+        runOnUiThread(() -> {
+            KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
+            mWebView.dispatchKeyEvent(keyEvent);
+            keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
+            mWebView.dispatchKeyEvent(keyEvent);
+        });
+        // wait webview to process the key event.
+        SystemClock.sleep(300);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AttachedContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AttachedContextActivity.java
new file mode 100644
index 0000000..47da817
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AttachedContextActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.widget.EditText;
+
+import java.util.Locale;
+
+/**
+ * Simple activity that attaches a new base context.
+ */
+public class AttachedContextActivity extends AbstractAutoFillActivity {
+    public static final String ID_INPUT = "input";
+
+    public EditText mInput;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_save_activity);
+
+        mInput = findViewById(R.id.input);
+    }
+
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        final Context localContext = applyLocale(newBase, "en");
+        super.attachBaseContext(localContext);
+    }
+
+    private Context applyLocale(Context context, String language) {
+        final Resources resources = context.getResources();
+        final Configuration configuration = resources.getConfiguration();
+        configuration.setLocale(new Locale(language));
+        return context.createConfigurationContext(configuration);
+    }
+
+    public FillExpectation expectAutoFill(String input) {
+        final FillExpectation expectation = new FillExpectation(input);
+        mInput.addTextChangedListener(expectation.mInputWatcher);
+        return expectation;
+    }
+
+    public final class FillExpectation {
+        private final OneTimeTextWatcher mInputWatcher;
+
+        private FillExpectation(String input) {
+            mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
+        }
+
+        public void assertAutoFilled() throws Exception {
+            mInputWatcher.assertAutoFilled();
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedAuthActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedAuthActivity.java
new file mode 100644
index 0000000..25a33d8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedAuthActivity.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.PendingIntent;
+import android.autofillservice.cts.R;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.service.autofill.Dataset;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Activity for testing Augmented Autofill authentication flow. This activity shows a simple UI;
+ * when the UI is tapped, it returns whatever data was configured via the auth intent.
+ */
+public class AugmentedAuthActivity extends AbstractAutoFillActivity {
+    private static final String TAG = "AugmentedAuthActivity";
+
+    public static final String ID_AUTH_ACTIVITY_BUTTON = "button";
+
+    private static final String EXTRA_DATASET_TO_RETURN = "dataset_to_return";
+    private static final String EXTRA_CLIENT_STATE_TO_RETURN = "client_state_to_return";
+    private static final String EXTRA_RESULT_CODE_TO_RETURN = "result_code_to_return";
+
+    private static final List<PendingIntent> sPendingIntents = new ArrayList<>(1);
+
+    public static void resetStaticState() {
+        for (PendingIntent pendingIntent : sPendingIntents) {
+            pendingIntent.cancel();
+        }
+        sPendingIntents.clear();
+    }
+
+    public static IntentSender createSender(Context context, int requestCode,
+            Dataset datasetToReturn, Bundle clientStateToReturn, int resultCodeToReturn) {
+        Intent intent = new Intent(context, AugmentedAuthActivity.class);
+        intent.putExtra(EXTRA_DATASET_TO_RETURN, datasetToReturn);
+        intent.putExtra(EXTRA_CLIENT_STATE_TO_RETURN, clientStateToReturn);
+        intent.putExtra(EXTRA_RESULT_CODE_TO_RETURN, resultCodeToReturn);
+        PendingIntent pendingIntent = PendingIntent.getActivity(context, requestCode, intent, 0);
+        sPendingIntents.add(pendingIntent);
+        return pendingIntent.getIntentSender();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Log.d(TAG, "Auth activity invoked, showing auth UI");
+        setContentView(R.layout.single_button_activity);
+        findViewById(R.id.button).setOnClickListener((v) -> {
+            Log.d(TAG, "Auth UI tapped, returning result");
+
+            Intent intent = getIntent();
+            Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET_TO_RETURN);
+            Bundle clientState = intent.getParcelableExtra(EXTRA_CLIENT_STATE_TO_RETURN);
+            int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE_TO_RETURN, RESULT_OK);
+            Log.d(TAG, "Output: dataset=" + dataset + ", clientState=" + clientState
+                    + ", resultCode=" + resultCode);
+
+            Intent result = new Intent();
+            result.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, dataset);
+            result.putExtra(AutofillManager.EXTRA_CLIENT_STATE, clientState);
+            setResult(resultCode, result);
+
+            finish();
+        });
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedLoginActivity.java
new file mode 100644
index 0000000..422ff81
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedLoginActivity.java
@@ -0,0 +1,21 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+// Currently it's same as LoginActivity, except that it allows rotation.
+public class AugmentedLoginActivity extends LoginActivity {
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
new file mode 100644
index 0000000..9a40ce5
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.NULL;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.autofill.AutofillManager;
+import android.widget.Button;
+import android.widget.EditText;
+
+import com.google.common.base.Preconditions;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class simulates authentication at the dataset at reponse level
+ */
+public class AuthenticationActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "AuthenticationActivity";
+    private static final String EXTRA_DATASET_ID = "dataset_id";
+    private static final String EXTRA_RESPONSE_ID = "response_id";
+
+    /**
+     * When launched with this intent, it will pass it back to the
+     * {@link AutofillManager#EXTRA_CLIENT_STATE} of the result.
+     */
+    private static final String EXTRA_OUTPUT_CLIENT_STATE = "output_client_state";
+
+
+    private static final int MSG_WAIT_FOR_LATCH = 1;
+    private static final int MSG_REQUEST_AUTOFILL = 2;
+
+    private static Bundle sData;
+    private static final SparseArray<CannedDataset> sDatasets = new SparseArray<>();
+    private static final SparseArray<CannedFillResponse> sResponses = new SparseArray<>();
+    private static final ArrayList<PendingIntent> sPendingIntents = new ArrayList<>();
+
+    private static Object sLock = new Object();
+
+    // Guarded by sLock
+    private static int sResultCode;
+
+    // Guarded by sLock
+    // Used to block response until it's counted down.
+    private static CountDownLatch sResponseLatch;
+
+    // Guarded by sLock
+    // Used to request autofill for a autofillable view in AuthenticationActivity
+    private static boolean sRequestAutofill;
+
+    private Handler mHandler;
+
+    private EditText mPasswordEditText;
+    private Button mYesButton;
+
+    public static void resetStaticState() {
+        setResultCode(null, RESULT_OK);
+        setRequestAutofillForAuthenticationActivity(/* requestAutofill */ false);
+        sDatasets.clear();
+        sResponses.clear();
+        for (int i = 0; i < sPendingIntents.size(); i++) {
+            final PendingIntent pendingIntent = sPendingIntents.get(i);
+            Log.d(TAG, "Cancelling " + pendingIntent);
+            pendingIntent.cancel();
+        }
+    }
+
+    /**
+     * Creates an {@link IntentSender} with the given unique id for the given dataset.
+     */
+    public static IntentSender createSender(Context context, int id, CannedDataset dataset) {
+        return createSender(context, id, dataset, null);
+    }
+
+    public static IntentSender createSender(Context context, int id,
+            CannedDataset dataset, Bundle outClientState) {
+        Preconditions.checkArgument(id > 0, "id must be positive");
+        Preconditions.checkState(sDatasets.get(id) == null, "already have id");
+        sDatasets.put(id, dataset);
+        return createSender(context, EXTRA_DATASET_ID, id, outClientState);
+    }
+
+    /**
+     * Creates an {@link IntentSender} with the given unique id for the given fill response.
+     */
+    public static IntentSender createSender(Context context, int id,
+            CannedFillResponse response) {
+        return createSender(context, id, response, null);
+    }
+
+    public static IntentSender createSender(Context context, int id,
+            CannedFillResponse response, Bundle outData) {
+        Preconditions.checkArgument(id > 0, "id must be positive");
+        Preconditions.checkState(sResponses.get(id) == null, "already have id");
+        sResponses.put(id, response);
+        return createSender(context, EXTRA_RESPONSE_ID, id, outData);
+    }
+
+    private static IntentSender createSender(Context context, String extraName, int id,
+            Bundle outClientState) {
+        final Intent intent = new Intent(context, AuthenticationActivity.class);
+        intent.putExtra(extraName, id);
+        if (outClientState != null) {
+            Log.d(TAG, "Create with " + outClientState + " as " + EXTRA_OUTPUT_CLIENT_STATE);
+            intent.putExtra(EXTRA_OUTPUT_CLIENT_STATE, outClientState);
+        }
+        final PendingIntent pendingIntent = PendingIntent.getActivity(context, id, intent, 0);
+        sPendingIntents.add(pendingIntent);
+        return pendingIntent.getIntentSender();
+    }
+
+    /**
+     * Creates an {@link IntentSender} with the given unique id.
+     */
+    public static IntentSender createSender(Context context, int id) {
+        Preconditions.checkArgument(id > 0, "id must be positive");
+        return PendingIntent
+                .getActivity(context, id, new Intent(context, AuthenticationActivity.class),
+                        PendingIntent.FLAG_CANCEL_CURRENT)
+                .getIntentSender();
+    }
+
+    public static Bundle getData() {
+        final Bundle data = sData;
+        sData = null;
+        return data;
+    }
+
+    /**
+     * Sets the value that's passed to {@link Activity#setResult(int, Intent)} when on
+     * {@link Activity#onCreate(Bundle)}.
+     */
+    public static void setResultCode(int resultCode) {
+        synchronized (sLock) {
+            sResultCode = resultCode;
+        }
+    }
+
+    /**
+     * Sets the value that's passed to {@link Activity#setResult(int, Intent)}, but only calls it
+     * after the {@code latch}'s countdown reaches {@code 0}.
+     */
+    public static void setResultCode(CountDownLatch latch, int resultCode) {
+        synchronized (sLock) {
+            sResponseLatch = latch;
+            sResultCode = resultCode;
+        }
+    }
+
+    public static void setRequestAutofillForAuthenticationActivity(boolean requestAutofill) {
+        synchronized (sLock) {
+            sRequestAutofill = requestAutofill;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.authentication_activity);
+
+        mPasswordEditText = findViewById(R.id.password);
+        mYesButton = findViewById(R.id.yes);
+        mYesButton.setOnClickListener(view -> doIt());
+
+        mHandler = new Handler(Looper.getMainLooper(), (m) -> {
+            switch (m.what) {
+                case MSG_WAIT_FOR_LATCH:
+                    waitForLatchAndDoIt();
+                    break;
+                case MSG_REQUEST_AUTOFILL:
+                    requestFocusOnPassword();
+                    break;
+                default:
+                    throw new IllegalArgumentException("invalid message: " + m);
+            }
+            return true;
+        });
+
+        if (sResponseLatch != null) {
+            Log.d(TAG, "Delaying message until latch is counted down");
+            mHandler.dispatchMessage(mHandler.obtainMessage(MSG_WAIT_FOR_LATCH));
+        } else if (sRequestAutofill) {
+            mHandler.dispatchMessage(mHandler.obtainMessage(MSG_REQUEST_AUTOFILL));
+        } else {
+            doIt();
+        }
+    }
+
+    private void requestFocusOnPassword() {
+        syncRunOnUiThread(() -> mPasswordEditText.requestFocus());
+    }
+
+    private void waitForLatchAndDoIt() {
+        try {
+            final boolean called = sResponseLatch.await(5, TimeUnit.SECONDS);
+            if (!called) {
+                throw new IllegalStateException("latch not called in 5 seconds");
+            }
+            doIt();
+        } catch (InterruptedException e) {
+            Thread.interrupted();
+            throw new IllegalStateException("interrupted");
+        }
+    }
+
+    private void doIt() {
+        // We should get the assist structure...
+        final AssistStructure structure = getIntent().getParcelableExtra(
+                AutofillManager.EXTRA_ASSIST_STRUCTURE);
+        assertWithMessage("structure not called").that(structure).isNotNull();
+
+        // and the bundle
+        sData = getIntent().getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE);
+        final CannedFillResponse response =
+                sResponses.get(getIntent().getIntExtra(EXTRA_RESPONSE_ID, 0));
+        final CannedDataset dataset =
+                sDatasets.get(getIntent().getIntExtra(EXTRA_DATASET_ID, 0));
+
+        final Parcelable result;
+
+        if (response != null) {
+            if (response.getResponseType() == NULL) {
+                result = null;
+            } else {
+                result = response.asFillResponse(/* contexts= */ null,
+                        (id) -> Helper.findNodeByResourceId(structure, id));
+            }
+        } else if (dataset != null) {
+            result = dataset.asDataset((id) -> Helper.findNodeByResourceId(structure, id));
+        } else {
+            throw new IllegalStateException("no dataset or response");
+        }
+
+        // Pass on the auth result
+        final Intent intent = new Intent();
+        intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, result);
+
+        final Bundle outClientState = getIntent().getBundleExtra(EXTRA_OUTPUT_CLIENT_STATE);
+        if (outClientState != null) {
+            Log.d(TAG, "Adding " + outClientState + " as " + AutofillManager.EXTRA_CLIENT_STATE);
+            intent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, outClientState);
+        }
+
+        final int resultCode;
+        synchronized (sLock) {
+            resultCode = sResultCode;
+        }
+        Log.d(TAG, "Returning code " + resultCode);
+        setResult(resultCode, intent);
+
+        // Done
+        finish();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/CheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/CheckoutActivity.java
new file mode 100644
index 0000000..61d2269
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/CheckoutActivity.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static android.widget.ArrayAdapter.createFromResource;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeCompoundButtonListener;
+import android.autofillservice.cts.testcore.OneTimeRadioGroupListener;
+import android.autofillservice.cts.testcore.OneTimeSpinnerListener;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Spinner;
+import android.widget.TimePicker;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity that has the following fields:
+ *
+ * <ul>
+ *   <li>Credit Card Number EditText (id: cc_numberusername, no input-type)
+ *   <li>Credit Card Expiration EditText (id: cc_expiration, no input-type)
+ *   <li>Address RadioGroup (id: addess, no autofill-type)
+ *   <li>Save Credit Card CheckBox (id: save_cc, no autofill-type)
+ *   <li>Clear Button
+ *   <li>Buy Button
+ *   <li>DatePicker
+ *   <li>TimePicker
+ * </ul>
+ */
+public class CheckoutActivity extends AbstractAutoFillActivity {
+    private static final long BUY_TIMEOUT_MS = 1000;
+
+    public static final String ID_CC_NUMBER = "cc_number";
+    public static final String ID_CC_EXPIRATION = "cc_expiration";
+    public static final String ID_ADDRESS = "address";
+    public static final String ID_HOME_ADDRESS = "home_address";
+    public static final String ID_WORK_ADDRESS = "work_address";
+    public static final String ID_SAVE_CC = "save_cc";
+    public static final String ID_DATE_PICKER = "datePicker";
+    public static final String ID_TIME_PICKER = "timePicker";
+
+    public static final int INDEX_ADDRESS_HOME = 0;
+    public static final int INDEX_ADDRESS_WORK = 1;
+
+    public static final int INDEX_CC_EXPIRATION_YESTERDAY = 0;
+    public static final int INDEX_CC_EXPIRATION_TODAY = 1;
+    public static final int INDEX_CC_EXPIRATION_TOMORROW = 2;
+    public static final int INDEX_CC_EXPIRATION_NEVER = 3;
+
+    private EditText mCcNumber;
+    private Spinner mCcExpiration;
+    private ArrayAdapter<CharSequence> mCcExpirationAdapter;
+    private RadioGroup mAddress;
+    private RadioButton mHomeAddress;
+    private RadioButton mWorkAddress;
+    private CheckBox mSaveCc;
+    private Button mBuyButton;
+    private Button mClearButton;
+    private DatePicker mDatePicker;
+    private TimePicker mTimePicker;
+
+    private FillExpectation mExpectation;
+    private CountDownLatch mBuyLatch;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(getContentView());
+
+        mCcNumber = findViewById(R.id.cc_number);
+        mCcExpiration = findViewById(R.id.cc_expiration);
+        mAddress = findViewById(R.id.address);
+        mHomeAddress = findViewById(R.id.home_address);
+        mWorkAddress = findViewById(R.id.work_address);
+        mSaveCc = findViewById(R.id.save_cc);
+        mBuyButton = findViewById(R.id.buy);
+        mClearButton = findViewById(R.id.clear);
+        mDatePicker = findViewById(R.id.datePicker);
+        mTimePicker = findViewById(R.id.timePicker);
+
+        mCcExpirationAdapter = createFromResource(this,
+                R.array.cc_expiration_values, android.R.layout.simple_spinner_item);
+        mCcExpirationAdapter
+                .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        mCcExpiration.setAdapter(mCcExpirationAdapter);
+
+        mBuyButton.setOnClickListener((v) -> buy());
+        mClearButton.setOnClickListener((v) -> resetFields());
+    }
+
+    protected int getContentView() {
+        return R.layout.checkout_activity;
+    }
+
+    /**
+     * Resets the values of the input fields.
+     */
+    private void resetFields() {
+        mCcNumber.setText("");
+        mCcExpiration.setSelection(0, false);
+        mAddress.clearCheck();
+        mSaveCc.setChecked(false);
+    }
+
+    /**
+     * Emulates a buy action.
+     */
+    private void buy() {
+        final Intent intent = new Intent(this, WelcomeActivity.class);
+        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Thank you an come again!");
+        startActivity(intent);
+        if (mBuyLatch != null) {
+            // Latch is not set when activity launched outside tests
+            mBuyLatch.countDown();
+        }
+        finish();
+    }
+
+    /**
+     * Sets the expectation for an auto-fill request, so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(String ccNumber, int ccExpirationIndex, int addressId,
+            boolean saveCc) {
+        mExpectation = new FillExpectation(ccNumber, ccExpirationIndex, addressId, saveCc);
+        mCcNumber.addTextChangedListener(mExpectation.ccNumberWatcher);
+        mCcExpiration.setOnItemSelectedListener(mExpectation.ccExpirationListener);
+        mAddress.setOnCheckedChangeListener(mExpectation.addressListener);
+        mSaveCc.setOnCheckedChangeListener(mExpectation.saveCcListener);
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, int, int, boolean)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        mExpectation.ccNumberWatcher.assertAutoFilled();
+        mExpectation.ccExpirationListener.assertAutoFilled();
+        mExpectation.addressListener.assertAutoFilled();
+        mExpectation.saveCcListener.assertAutoFilled();
+    }
+
+    /**
+     * Visits the {@code ccNumber} in the UiThread.
+     */
+    public void onCcNumber(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mCcNumber));
+    }
+
+    /**
+     * Visits the {@code ccExpirationDate} in the UiThread.
+     */
+    public void onCcExpiration(Visitor<Spinner> v) {
+        syncRunOnUiThread(() -> v.visit(mCcExpiration));
+    }
+
+    /**
+     * Visits the {@code ccExpirationDate} adapter in the UiThread.
+     */
+    public void onCcExpirationAdapter(Visitor<ArrayAdapter<CharSequence>> v) {
+        syncRunOnUiThread(() -> v.visit(mCcExpirationAdapter));
+    }
+
+    /**
+     * Visits the {@code address} in the UiThread.
+     */
+    public void onAddress(Visitor<RadioGroup> v) {
+        syncRunOnUiThread(() -> v.visit(mAddress));
+    }
+
+    /**
+     * Visits the {@code homeAddress} in the UiThread.
+     */
+    public void onHomeAddress(Visitor<RadioButton> v) {
+        syncRunOnUiThread(() -> v.visit(mHomeAddress));
+    }
+
+    /**
+     * Visits the {@code workAddress} in the UiThread.
+     */
+    public void onWorkAddress(Visitor<RadioButton> v) {
+        syncRunOnUiThread(() -> v.visit(mWorkAddress));
+    }
+
+    /**
+     * Visits the {@code saveCC} in the UiThread.
+     */
+    public void onSaveCc(Visitor<CheckBox> v) {
+        syncRunOnUiThread(() -> v.visit(mSaveCc));
+    }
+
+    /**
+     * Taps the buy button in the UI thread.
+     */
+    public void tapBuy() throws Exception {
+        mBuyLatch = new CountDownLatch(1);
+        syncRunOnUiThread(() -> mBuyButton.performClick());
+        boolean called = mBuyLatch.await(BUY_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) waiting for buy action", BUY_TIMEOUT_MS)
+                .that(called).isTrue();
+    }
+
+    public EditText getCcNumber() {
+        return mCcNumber;
+    }
+
+    public Spinner getCcExpiration() {
+        return mCcExpiration;
+    }
+
+    public CheckBox getSaveCc() {
+        return mSaveCc;
+    }
+
+    public RadioGroup getAddress() {
+        return mAddress;
+    }
+
+    public DatePicker getDatePicker() {
+        return mDatePicker;
+    }
+
+    public TimePicker getTimePicker() {
+        return mTimePicker;
+    }
+
+    public void assertRadioButtonValue(boolean homeAddrValue, boolean workAddrValue)
+            throws Exception {
+        assertThat(mHomeAddress.isChecked()).isEqualTo(homeAddrValue);
+        assertThat(mWorkAddress.isChecked()).isEqualTo(workAddrValue);
+    }
+
+    public ArrayAdapter<CharSequence> getCcExpirationAdapter() {
+        return mCcExpirationAdapter;
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final OneTimeTextWatcher ccNumberWatcher;
+        private final OneTimeSpinnerListener ccExpirationListener;
+        private final OneTimeRadioGroupListener addressListener;
+        private final OneTimeCompoundButtonListener saveCcListener;
+
+        private FillExpectation(String ccNumber, int ccExpirationIndex, int addressId,
+                boolean saveCc) {
+            this.ccNumberWatcher = new OneTimeTextWatcher("ccNumber", mCcNumber, ccNumber);
+            this.ccExpirationListener =
+                    new OneTimeSpinnerListener("ccExpiration", mCcExpiration, ccExpirationIndex);
+            addressListener = new OneTimeRadioGroupListener("address", mAddress, addressId);
+            saveCcListener = new OneTimeCompoundButtonListener("saveCc", mSaveCc, saveCc);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerCalendarActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerCalendarActivity.java
new file mode 100644
index 0000000..7ccf760
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerCalendarActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+public class DatePickerCalendarActivity extends AbstractDatePickerActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.date_picker_calendar_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerSpinnerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerSpinnerActivity.java
new file mode 100644
index 0000000..729f1d3
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerSpinnerActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+public class DatePickerSpinnerActivity extends AbstractDatePickerActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.date_picker_spinner_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/DialogLauncherActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/DialogLauncherActivity.java
new file mode 100644
index 0000000..d986328
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/DialogLauncherActivity.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.AlertDialog;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.UiBot;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+
+/**
+ * Activity that has buttons to launch dialogs that should then be autofillable.
+ */
+public class DialogLauncherActivity extends AbstractAutoFillActivity {
+
+    private FillExpectation mExpectation;
+    private LoginDialog mDialog;
+    Button mLaunchButton;
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.dialog_launcher_activity);
+        mLaunchButton = findViewById(R.id.launch_button);
+        mDialog = new LoginDialog(this);
+        mLaunchButton.setOnClickListener((v) -> mDialog.show());
+    }
+
+    public void onUsername(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mDialog.mUsernameEditText));
+    }
+
+    public void launchDialog(UiBot uiBot) throws Exception {
+        syncRunOnUiThread(() -> mLaunchButton.performClick());
+        // TODO: should assert by id, but it's not working
+        uiBot.assertShownByText("Username");
+    }
+
+    public void assertInDialogBounds(Rect rect) {
+        final int[] location = new int[2];
+        final View view = mDialog.getWindow().getDecorView();
+        view.getLocationOnScreen(location);
+        assertThat(location[0]).isAtMost(rect.left);
+        assertThat(rect.right).isAtMost(location[0] + view.getWidth());
+        assertThat(location[1]).isAtMost(rect.top);
+        assertThat(rect.bottom).isAtMost(location[1] + view.getHeight());
+    }
+
+    public void maximizeDialog() {
+        final WindowManager wm = getWindowManager();
+        final Display display = wm.getDefaultDisplay();
+        final DisplayMetrics metrics = new DisplayMetrics();
+        display.getMetrics(metrics);
+        syncRunOnUiThread(
+                () -> mDialog.getWindow().setLayout(metrics.widthPixels, metrics.heightPixels));
+    }
+
+    public void expectAutofill(String username, String password) {
+        assertWithMessage("must call launchDialog first").that(mDialog.mUsernameEditText)
+                .isNotNull();
+        mExpectation = new FillExpectation(username, password);
+        mDialog.mUsernameEditText.addTextChangedListener(mExpectation.mCcUsernameWatcher);
+        mDialog.mPasswordEditText.addTextChangedListener(mExpectation.mCcPasswordWatcher);
+    }
+
+    public void assertAutofilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        if (mExpectation.mCcUsernameWatcher != null) {
+            mExpectation.mCcUsernameWatcher.assertAutoFilled();
+        }
+        if (mExpectation.mCcPasswordWatcher != null) {
+            mExpectation.mCcPasswordWatcher.assertAutoFilled();
+        }
+    }
+
+    private final class FillExpectation {
+        private final OneTimeTextWatcher mCcUsernameWatcher;
+        private final OneTimeTextWatcher mCcPasswordWatcher;
+
+        private FillExpectation(String username, String password) {
+            mCcUsernameWatcher = username == null ? null
+                    : new OneTimeTextWatcher("username", mDialog.mUsernameEditText, username);
+            mCcPasswordWatcher = password == null ? null
+                    : new OneTimeTextWatcher("password", mDialog.mPasswordEditText, password);
+        }
+
+        private FillExpectation(String username) {
+            this(username, null);
+        }
+    }
+
+    public final class LoginDialog extends AlertDialog {
+
+        private EditText mUsernameEditText;
+        private EditText mPasswordEditText;
+
+        public LoginDialog(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            setContentView(R.layout.login_activity);
+            mUsernameEditText = findViewById(R.id.username);
+            mPasswordEditText = findViewById(R.id.password);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/DummyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/DummyActivity.java
new file mode 100644
index 0000000..3832b5d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/DummyActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class DummyActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        TextView text = new TextView(this);
+        text.setText("foo");
+        setContentView(text);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/DuplicateIdActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/DuplicateIdActivity.java
new file mode 100644
index 0000000..f30d3ed
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/DuplicateIdActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+
+public class DuplicateIdActivity extends AbstractAutoFillActivity {
+    private static final String TAG = "DuplicateIdActivity";
+
+    public static final String DUPLICATE_ID = "duplicate_id";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Log.v(TAG, "onCreate(" + savedInstanceState + ")");
+
+        setContentView(R.layout.duplicate_id_layout);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/EmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/EmptyActivity.java
new file mode 100644
index 0000000..26f18b4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/EmptyActivity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Activity;
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Empty activity
+ */
+public class EmptyActivity extends Activity {
+
+    public static final String ID_EMPTY = "empty";
+
+    private View mEmptyView;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.empty);
+        mEmptyView = findViewById(R.id.empty);
+    }
+
+    public View getEmptyView() {
+        return mEmptyView;
+    }
+
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FatActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FatActivity.java
new file mode 100644
index 0000000..69bf8e4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FatActivity.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Helper.findViewByAutofillHint;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+/**
+ * An activity containing mostly widgets that should be removed from an auto-fill structure to
+ * optimize it.
+ */
+public class FatActivity extends AbstractAutoFillActivity {
+
+    public static final String ID_CAPTCHA = "captcha";
+    public static final String ID_INPUT = "input";
+    public static final String ID_INPUT_CONTAINER = "input_container";
+    public static final String ID_IMAGE = "image";
+    public static final String ID_IMPORTANT_IMAGE = "important_image";
+    public static final String ID_ROOT = "root";
+
+    public static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS =
+            "not_important_container_excluding_descendants";
+    public static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD =
+            "not_important_container_excluding_descendants_child";
+    public static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD =
+            "not_important_container_excluding_descendants_grand_child";
+
+    public static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS =
+            "important_container_excluding_descendants";
+    public static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD =
+            "important_container_excluding_descendants_child";
+    public static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD =
+            "important_container_excluding_descendants_grand_child";
+
+    public static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS =
+            "not_important_container_mixed_descendants";
+    public static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD =
+            "not_important_container_mixed_descendants_child";
+    public static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD =
+            "not_important_container_mixed_descendants_grand_child";
+
+    private LinearLayout mRoot;
+    private EditText mCaptcha;
+    private EditText mInput;
+    private ImageView mImage;
+    private ImageView mImportantImage;
+
+    private View mNotImportantContainerExcludingDescendants;
+    private View mNotImportantContainerExcludingDescendantsChild;
+    private View mNotImportantContainerExcludingDescendantsGrandChild;
+
+    private View mImportantContainerExcludingDescendants;
+    private View mImportantContainerExcludingDescendantsChild;
+    private View mImportantContainerExcludingDescendantsGrandChild;
+
+    private View mNotImportantContainerMixedDescendants;
+    private View mNotImportantContainerMixedDescendantsChild;
+    private View mNotImportantContainerMixedDescendantsGrandChild;
+
+    private MyView mViewWithAutofillHints;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.fat_activity);
+
+        mRoot = findViewById(R.id.root);
+        mCaptcha = findViewById(R.id.captcha);
+        mInput = findViewById(R.id.input);
+        mImage = findViewById(R.id.image);
+        mImportantImage = findViewById(R.id.important_image);
+
+        mNotImportantContainerExcludingDescendants = findViewById(
+                R.id.not_important_container_excluding_descendants);
+        mNotImportantContainerExcludingDescendantsChild = findViewById(
+                R.id.not_important_container_excluding_descendants_child);
+        mNotImportantContainerExcludingDescendantsGrandChild = findViewById(
+                R.id.not_important_container_excluding_descendants_grand_child);
+
+        mImportantContainerExcludingDescendants = findViewById(
+                R.id.important_container_excluding_descendants);
+        mImportantContainerExcludingDescendantsChild = findViewById(
+                R.id.important_container_excluding_descendants_child);
+        mImportantContainerExcludingDescendantsGrandChild = findViewById(
+                R.id.important_container_excluding_descendants_grand_child);
+
+        mNotImportantContainerMixedDescendants = findViewById(
+                R.id.not_important_container_mixed_descendants);
+        mNotImportantContainerMixedDescendantsChild = findViewById(
+                R.id.not_important_container_mixed_descendants_child);
+        mNotImportantContainerMixedDescendantsGrandChild = findViewById(
+                R.id.not_important_container_mixed_descendants_grand_child);
+
+        mViewWithAutofillHints = (MyView) findViewByAutofillHint(this, "importantAmI");
+        assertThat(mViewWithAutofillHints).isNotNull();
+
+        // Validation check for importantForAutofill modes
+        assertThat(mRoot.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
+        assertThat(mInput.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+        assertThat(mCaptcha.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
+        assertThat(mImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
+        assertThat(mImportantImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+
+        assertThat(mNotImportantContainerExcludingDescendants.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
+        assertThat(mNotImportantContainerExcludingDescendantsChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+        assertThat(mNotImportantContainerExcludingDescendantsGrandChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
+
+        assertThat(mImportantContainerExcludingDescendants.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
+        assertThat(mImportantContainerExcludingDescendantsChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+        assertThat(mImportantContainerExcludingDescendantsGrandChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
+
+        assertThat(mNotImportantContainerMixedDescendants.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
+        assertThat(mNotImportantContainerMixedDescendantsChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+        assertThat(mNotImportantContainerMixedDescendantsGrandChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
+
+        assertThat(mViewWithAutofillHints.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
+        assertThat(mViewWithAutofillHints.isImportantForAutofill()).isTrue();
+    }
+
+    /**
+     * Visits the {@code input} in the UiThread.
+     */
+    public void onInput(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> {
+            v.visit(mInput);
+        });
+    }
+
+    /**
+     * Custom view that defines an autofill type so autofill hints are set on {@code ViewNode}.
+     */
+    public static class MyView extends View {
+        public MyView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @Override
+        public int getAutofillType() {
+            return AUTOFILL_TYPE_TEXT;
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentContainerActivity.java
new file mode 100644
index 0000000..a2edf71
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentContainerActivity.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.os.Bundle;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity containing an fragment
+ */
+public class FragmentContainerActivity extends AbstractAutoFillActivity {
+    public static final String FRAGMENT_TAG =
+            FragmentContainerActivity.class.getName() + "#FRAGMENT_TAG";
+    private CountDownLatch mResumed = new CountDownLatch(1);
+    private CountDownLatch mStopped = new CountDownLatch(0);
+    private FrameLayout mRootContainer;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.fragment_container);
+
+        mRootContainer = findViewById(R.id.rootContainer);
+
+        // have to manually add fragment as we cannot remove it otherwise
+        getFragmentManager().beginTransaction().add(R.id.rootContainer,
+                new FragmentWithEditText(), FRAGMENT_TAG).commitNow();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        mStopped = new CountDownLatch(1);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        mResumed.countDown();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        mResumed = new CountDownLatch(1);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        mStopped.countDown();
+    }
+
+    /**
+     * Sets whether the root container is focusable or not.
+     *
+     * <p>It's initially set as {@code trye} in the XML layout so autofill is not automatically
+     * triggered in the edit text before the service is prepared to handle it.
+     */
+    public void setRootContainerFocusable(boolean focusable) {
+        mRootContainer.setFocusable(focusable);
+        mRootContainer.setFocusableInTouchMode(focusable);
+    }
+
+    public boolean waitUntilResumed() throws InterruptedException {
+        return mResumed.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+    }
+
+    public boolean waitUntilStopped() throws InterruptedException {
+        return mStopped.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithEditText.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithEditText.java
new file mode 100644
index 0000000..c615854
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithEditText.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Fragment;
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A fragment with containing {@link EditText}s
+ */
+public class FragmentWithEditText extends Fragment {
+    @Override
+    @Nullable
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.fragment_with_edittext, null);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithMoreEditTexts.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithMoreEditTexts.java
new file mode 100644
index 0000000..a562e2b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithMoreEditTexts.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Fragment;
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A fragment with containing more {@link EditText}s
+ */
+public class FragmentWithMoreEditTexts extends Fragment {
+    @Override
+    @Nullable
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.fragment_with_more_edittexts, null);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/GridActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/GridActivity.java
new file mode 100644
index 0000000..696810e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/GridActivity.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.Visitor;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.GridLayout;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.ArrayList;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity that contains a 4x4 grid of cells (named {@code l1c1} to {@code l4c2}) plus
+ * {@code save} and {@code clear} buttons.
+ */
+public class GridActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "GridActivity";
+    private static final int N_ROWS = 4;
+    private static final int N_COLS = 2;
+
+    public static final String ID_L1C1 = getResourceId(1, 1);
+    public static final String ID_L1C2 = getResourceId(1, 2);
+    public static final String ID_L2C1 = getResourceId(2, 1);
+    public static final String ID_L2C2 = getResourceId(2, 2);
+    public static final String ID_L3C1 = getResourceId(3, 1);
+    public static final String ID_L3C2 = getResourceId(3, 2);
+    public static final String ID_L4C1 = getResourceId(4, 1);
+    public static final String ID_L4C2 = getResourceId(4, 2);
+
+    private GridLayout mGrid;
+    private final EditText[][] mCells = new EditText[4][2];
+    private Button mSaveButton;
+    private Button mClearButton;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.grid_activity);
+
+        mGrid = findViewById(R.id.grid);
+        mCells[0][0] = findViewById(R.id.l1c1);
+        mCells[0][1] = findViewById(R.id.l1c2);
+        mCells[1][0] = findViewById(R.id.l2c1);
+        mCells[1][1] = findViewById(R.id.l2c2);
+        mCells[2][0] = findViewById(R.id.l3c1);
+        mCells[2][1] = findViewById(R.id.l3c2);
+        mCells[3][0] = findViewById(R.id.l4c1);
+        mCells[3][1] = findViewById(R.id.l4c2);
+        mSaveButton = findViewById(R.id.save);
+        mClearButton = findViewById(R.id.clear);
+
+        mSaveButton.setOnClickListener((v) -> save());
+        mClearButton.setOnClickListener((v) -> resetFields());
+    }
+
+    public void save() {
+        getSystemService(AutofillManager.class).commit();
+    }
+
+    public void resetFields() {
+        for (int i = 0; i < N_ROWS; i++) {
+            for (int j = 0; j < N_COLS; j++) {
+                mCells[i][j].setText("");
+            }
+        }
+        getSystemService(AutofillManager.class).cancel();
+    }
+
+    public EditText getCell(int row, int column) {
+        return mCells[row - 1][column - 1];
+    }
+
+    public static String getResourceId(int line, int col) {
+        return "l" + line + "c" + col;
+    }
+
+    public void onCell(int row, int column, Visitor<EditText> v) {
+        final EditText cell = getCell(row, column);
+        syncRunOnUiThread(() -> v.visit(cell));
+    }
+
+    public void focusCell(int row, int column) {
+        onCell(row, column, EditText::requestFocus);
+    }
+
+    public void clearCell(int row, int column) {
+        onCell(row, column, (c) -> c.setText(""));
+    }
+
+    public void setText(int row, int column, String text) {
+        onCell(row, column, (c) -> c.setText(text));
+    }
+
+    public void forceAutofill(int row, int column) {
+        onCell(row, column, (c) -> getAutofillManager().requestAutofill(c));
+    }
+
+    public void removeCell(int row, int column) {
+        onCell(row, column, (c) -> mGrid.removeView(c));
+    }
+
+    public void addCell(int row, int column, EditText cell) {
+        mCells[row - 1][column - 1] = cell;
+        // TODO: ideally it should be added in the right place...
+        syncRunOnUiThread(() -> mGrid.addView(cell));
+    }
+
+    public void triggerAutofill(boolean manually, int row, int column) {
+        if (manually) {
+            forceAutofill(row, column);
+        } else {
+            focusCell(row, column);
+        }
+    }
+
+    public String getText(int row, int column) throws InterruptedException {
+        final long timeoutMs = 100;
+        final BlockingQueue<String> queue = new LinkedBlockingQueue<>(1);
+        onCell(row, column, (c) -> Helper.offer(queue, c.getText().toString(), timeoutMs));
+        final String text = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        if (text == null) {
+            throw new RetryableException("text not set in " + timeoutMs + "ms");
+        }
+        return text;
+    }
+
+    public FillExpectation expectAutofill() {
+        return new FillExpectation();
+    }
+
+    public void dumpCells() {
+        final StringBuilder output = new StringBuilder("dumpCells():\n");
+        for (int i = 0; i < N_ROWS; i++) {
+            for (int j = 0; j < N_COLS; j++) {
+                final String id = getResourceId(i + 1, j + 1);
+                final String value = mCells[i][j].getText().toString();
+                output.append('\t').append(id).append("='").append(value).append("'\n");
+            }
+        }
+        Log.d(TAG, output.toString());
+    }
+
+    public final class FillExpectation {
+
+        private final ArrayList<OneTimeTextWatcher> mWatchers = new ArrayList<>();
+
+        public FillExpectation onCell(int line, int col, String value) {
+            final String resourceId = getResourceId(line, col);
+            final EditText cell = getCell(line, col);
+            final OneTimeTextWatcher watcher = new OneTimeTextWatcher(resourceId, cell, value);
+            mWatchers.add(watcher);
+            cell.addTextChangedListener(watcher);
+            return this;
+        }
+
+        public void assertAutoFilled() throws Exception {
+            try {
+                for (int i = 0; i < mWatchers.size(); i++) {
+                    final OneTimeTextWatcher watcher = mWatchers.get(i);
+                    watcher.assertAutoFilled();
+                }
+            } catch (AssertionError | Exception e) {
+                dumpCells();
+                throw e;
+            }
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/InitializedCheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/InitializedCheckoutActivity.java
new file mode 100644
index 0000000..0b153d2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/InitializedCheckoutActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+public class InitializedCheckoutActivity extends CheckoutActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.initialized_checkout_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java
new file mode 100644
index 0000000..af44058
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity that has the following fields:
+ *
+ * <ul>
+ *   <li>Username EditText (id: username, no input-type)
+ *   <li>Password EditText (id: "username", input-type textPassword)
+ *   <li>Clear Button
+ *   <li>Save Button
+ *   <li>Login Button
+ * </ul>
+ */
+public class LoginActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "LoginActivity";
+    private static final long LOGIN_TIMEOUT_MS = 1000;
+
+    public static final String ID_USERNAME_CONTAINER = "username_container";
+    public static final String AUTHENTICATION_MESSAGE = "Authentication failed. D'OH!";
+    public static final String BACKDOOR_USERNAME = "LemmeIn";
+    public static final String BACKDOOR_PASSWORD_SUBSTRING = "pass";
+
+    private static String sWelcomeTemplate = "Welcome to the new activity, %s!";
+
+    private static LoginActivity sCurrentActivity;
+
+    private LinearLayout mUsernameContainer;
+    private TextView mUsernameLabel;
+    private EditText mUsernameEditText;
+    private TextView mPasswordLabel;
+    private EditText mPasswordEditText;
+    private TextView mOutput;
+    private Button mLoginButton;
+    private Button mSaveButton;
+    private Button mCancelButton;
+    private Button mClearButton;
+    private FillExpectation mExpectation;
+
+    // State used to synchronously get the result of a login attempt.
+    private CountDownLatch mLoginLatch;
+    private String mLoginMessage;
+
+    /**
+     * Gets the expected welcome message for a given username.
+     */
+    public static String getWelcomeMessage(String username) {
+        return String.format(sWelcomeTemplate,  username);
+    }
+
+    /**
+     * Gests the latest instance.
+     *
+     * <p>Typically used in test cases that rotates the activity
+     */
+    @SuppressWarnings("unchecked") // Its up to caller to make sure it's setting the right one
+    public static <T extends LoginActivity> T getCurrentActivity() {
+        return (T) sCurrentActivity;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(getContentView());
+
+        mUsernameContainer = findViewById(R.id.username_container);
+        mLoginButton = findViewById(R.id.login);
+        mSaveButton = findViewById(R.id.save);
+        mClearButton = findViewById(R.id.clear);
+        mCancelButton = findViewById(R.id.cancel);
+        mUsernameLabel = findViewById(R.id.username_label);
+        mUsernameEditText = findViewById(R.id.username);
+        mPasswordLabel = findViewById(R.id.password_label);
+        mPasswordEditText = findViewById(R.id.password);
+        mOutput = findViewById(R.id.output);
+
+        mLoginButton.setOnClickListener((v) -> login());
+        mSaveButton.setOnClickListener((v) -> save());
+        mClearButton.setOnClickListener((v) -> {
+            mUsernameEditText.setText("");
+            mPasswordEditText.setText("");
+            mOutput.setText("");
+            getAutofillManager().cancel();
+        });
+        mCancelButton.setOnClickListener((OnClickListener) v -> finish());
+
+        sCurrentActivity = this;
+    }
+
+    protected int getContentView() {
+        return R.layout.login_activity;
+    }
+
+    /**
+     * Emulates a login action.
+     */
+    private void login() {
+        final String username = mUsernameEditText.getText().toString();
+        final String password = mPasswordEditText.getText().toString();
+        final boolean valid = username.equals(password)
+                || (TextUtils.isEmpty(username) && TextUtils.isEmpty(password))
+                || password.contains(BACKDOOR_PASSWORD_SUBSTRING)
+                || username.equals(BACKDOOR_USERNAME);
+
+        if (valid) {
+            Log.d(TAG, "login ok: " + username);
+            final Intent intent = new Intent(this, WelcomeActivity.class);
+            final String message = getWelcomeMessage(username);
+            intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, message);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            setLoginMessage(message);
+            startActivity(intent);
+            finish();
+        } else {
+            Log.d(TAG, "login failed: " + AUTHENTICATION_MESSAGE);
+            mOutput.setText(AUTHENTICATION_MESSAGE);
+            setLoginMessage(AUTHENTICATION_MESSAGE);
+        }
+    }
+
+    private void setLoginMessage(String message) {
+        Log.d(TAG, "setLoginMessage(): " + message);
+        if (mLoginLatch != null) {
+            mLoginMessage = message;
+            mLoginLatch.countDown();
+        }
+    }
+
+    /**
+     * Explicitly forces the AutofillManager to save the username and password.
+     */
+    private void save() {
+        final InputMethodManager imm = (InputMethodManager) getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        imm.hideSoftInputFromWindow(mUsernameEditText.getWindowToken(), 0);
+        getAutofillManager().commit();
+    }
+
+    /**
+     * Sets the expectation for an autofill request (for all fields), so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(String username, String password) {
+        mExpectation = new FillExpectation(username, password);
+        mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher);
+        mPasswordEditText.addTextChangedListener(mExpectation.ccPasswordWatcher);
+    }
+
+    /**
+     * Sets the expectation for an autofill request (for username only), so it can be asserted
+     * through {@link #assertAutoFilled()} later.
+     *
+     * <p><strong>NOTE: </strong>This method checks the result of text change, it should not call
+     * this method too early, it may cause test fail. Call this method before checking autofill
+     * behavior.
+     * <pre>
+     * An example usage is:
+     * <code>
+     *  public void testAutofill() throws Exception {
+     *      // Enable service and trigger autofill
+     *      enableService();
+     *      final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+     *                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
+     *                         .setField(ID_USERNAME, "test")
+     *                         .setField(ID_PASSWORD, "tweet")
+     *                         .setPresentation(createPresentation("Second Dude"))
+     *                         .setInlinePresentation(createInlinePresentation("Second Dude"))
+     *                         .build());
+     *      sReplier.addResponse(builder.build());
+     *      mUiBot.selectByRelativeId(ID_USERNAME);
+     *      sReplier.getNextFillRequest();
+     *      // Filter suggestion
+     *      mActivity.onUsername((v) -> v.setText("t"));
+     *      mUiBot.assertDatasets("Second Dude");
+     *
+     *      // Call expectAutoFill() before checking autofill behavior
+     *      mActivity.expectAutoFill("test", "tweet");
+     *      mUiBot.selectDataset("Second Dude");
+     *      mActivity.assertAutoFilled();
+     *  }
+     * </code>
+     * </pre>
+     */
+    public void expectAutoFill(String username) {
+        mExpectation = new FillExpectation(username);
+        mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher);
+    }
+
+    /**
+     * Sets the expectation for an autofill request (for password only), so it can be asserted
+     * through {@link #assertAutoFilled()} later.
+     *
+     * <p><strong>NOTE: </strong>This method checks the result of text change, it should not call
+     * this method too early, it may cause test fail. Call this method before checking autofill
+     * behavior. {@See #expectAutoFill(String)} for how it should be used.
+     */
+    public void expectPasswordAutoFill(String password) {
+        mExpectation = new FillExpectation(null, password);
+        mPasswordEditText.addTextChangedListener(mExpectation.ccPasswordWatcher);
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, String)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        if (mExpectation.ccUsernameWatcher != null) {
+            mExpectation.ccUsernameWatcher.assertAutoFilled();
+        }
+        if (mExpectation.ccPasswordWatcher != null) {
+            mExpectation.ccPasswordWatcher.assertAutoFilled();
+        }
+    }
+
+    public void forceAutofillOnUsername() {
+        syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mUsernameEditText));
+    }
+
+    public void forceAutofillOnPassword() {
+        syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mPasswordEditText));
+    }
+
+    /**
+     * Visits the {@code username_label} in the UiThread.
+     */
+    public void onUsernameLabel(Visitor<TextView> v) {
+        syncRunOnUiThread(() -> v.visit(mUsernameLabel));
+    }
+
+    /**
+     * Visits the {@code username} in the UiThread.
+     */
+    public void onUsername(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mUsernameEditText));
+    }
+
+    @Override
+    public void clearFocus() {
+        syncRunOnUiThread(() -> ((View) mUsernameContainer.getParent()).requestFocus());
+    }
+
+    /**
+     * Gets the {@code username_label} view.
+     */
+    public TextView getUsernameLabel() {
+        return mUsernameLabel;
+    }
+
+    /**
+     * Gets the {@code username} view.
+     */
+    public EditText getUsername() {
+        return mUsernameEditText;
+    }
+
+    /**
+     * Visits the {@code password_label} in the UiThread.
+     */
+    public void onPasswordLabel(Visitor<TextView> v) {
+        syncRunOnUiThread(() -> v.visit(mPasswordLabel));
+    }
+
+    /**
+     * Visits the {@code password} in the UiThread.
+     */
+    public void onPassword(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mPasswordEditText));
+    }
+
+    /**
+     * Visits the {@code login} button in the UiThread.
+     */
+    public void onLogin(Visitor<Button> v) {
+        syncRunOnUiThread(() -> v.visit(mLoginButton));
+    }
+
+    /**
+     * Gets the {@code password} view.
+     */
+    public EditText getPassword() {
+        return mPasswordEditText;
+    }
+
+    /**
+     * Taps the login button in the UI thread.
+     */
+    public String tapLogin() throws Exception {
+        mLoginLatch = new CountDownLatch(1);
+        syncRunOnUiThread(() -> mLoginButton.performClick());
+        boolean called = mLoginLatch.await(LOGIN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) waiting for login", LOGIN_TIMEOUT_MS)
+                .that(called).isTrue();
+        return mLoginMessage;
+    }
+
+    /**
+     * Taps the save button in the UI thread.
+     */
+    public void tapSave() throws Exception {
+        syncRunOnUiThread(() -> mSaveButton.performClick());
+    }
+
+    /**
+     * Taps the clear button in the UI thread.
+     */
+    public void tapClear() {
+        syncRunOnUiThread(() -> mClearButton.performClick());
+    }
+
+    /**
+     * Sets the window flags.
+     */
+    public void setFlags(int flags) {
+        Log.d(TAG, "setFlags():" + flags);
+        syncRunOnUiThread(() -> getWindow().setFlags(flags, flags));
+    }
+
+    /**
+     * Adds a child view to the root container.
+     */
+    public void addChild(View child) {
+        Log.d(TAG, "addChild(" + child + "): id=" + child.getAutofillId());
+        final ViewGroup root = (ViewGroup) mUsernameContainer.getParent();
+        syncRunOnUiThread(() -> root.addView(child));
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final OneTimeTextWatcher ccUsernameWatcher;
+        private final OneTimeTextWatcher ccPasswordWatcher;
+
+        private FillExpectation(String username, String password) {
+            ccUsernameWatcher = username == null ? null
+                    : new OneTimeTextWatcher("username", mUsernameEditText, username);
+            ccPasswordWatcher = password == null ? null
+                    : new OneTimeTextWatcher("password", mPasswordEditText, password);
+        }
+
+        private FillExpectation(String username) {
+            this(username, null);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillActivity.java
new file mode 100644
index 0000000..bfc1713
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+/**
+ * Same as {@link LoginActivity}, but with autofill disabled.
+ */
+public class LoginNotImportantForAutofillActivity extends LoginActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.login_activity_not_important;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedActivityContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedActivityContextActivity.java
new file mode 100644
index 0000000..6114ad2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedActivityContextActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.util.Log;
+
+/**
+ * Same as {@link LoginNotImportantForAutofillActivity}, but using a context wrapper of itself
+ * as the base context.
+ */
+public class LoginNotImportantForAutofillWrappedActivityContextActivity
+        extends LoginNotImportantForAutofillActivity {
+
+    private Context mMyBaseContext;
+
+    @Override
+    public Context getBaseContext() {
+        if (mMyBaseContext == null) {
+            mMyBaseContext = new ContextWrapper(super.getBaseContext());
+            Log.d(mTag, "getBaseContext(): set to " + mMyBaseContext + " (instead of "
+                    + super.getBaseContext() + ")");
+        }
+        return mMyBaseContext;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedApplicationContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedApplicationContextActivity.java
new file mode 100644
index 0000000..bea6f88
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedApplicationContextActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.util.Log;
+
+/**
+ * Same as {@link LoginNotImportantForAutofillActivity}, but using a context wrapper of itself
+ * as the base context.
+ */
+public class LoginNotImportantForAutofillWrappedApplicationContextActivity
+        extends LoginNotImportantForAutofillActivity {
+
+    private Context mMyBaseContext;
+
+    @Override
+    public Context getBaseContext() {
+        if (mMyBaseContext == null) {
+            mMyBaseContext = new ContextWrapper(getApplicationContext());
+            Log.d(mTag, "getBaseContext(): set to " + mMyBaseContext + " (instead of "
+                    + super.getBaseContext() + ")");
+        }
+        return mMyBaseContext;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithCustomHighlightActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithCustomHighlightActivity.java
new file mode 100644
index 0000000..1f151ca
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithCustomHighlightActivity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+/**
+ * Same as {@link LoginActivity}, but with a custom autofill highlight drawable.
+ */
+public class LoginWithCustomHighlightActivity extends LoginActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.login_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithStringsActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithStringsActivity.java
new file mode 100644
index 0000000..032b5a8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithStringsActivity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+/**
+ * Same as {@link LoginActivity}, but with the texts for some fields set from resources.
+ */
+public class LoginWithStringsActivity extends LoginActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.login_with_strings_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java
new file mode 100644
index 0000000..58b67d7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Activity;
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.view.autofill.AutofillManager;
+
+/**
+ * An activity that authenticates on button press
+ */
+public class ManualAuthenticationActivity extends Activity {
+    private static CannedFillResponse sResponse;
+    private static CannedFillResponse.CannedDataset sDataset;
+
+    public static void setResponse(CannedFillResponse response) {
+        sResponse = response;
+        sDataset = null;
+    }
+
+    public static void setDataset(CannedFillResponse.CannedDataset dataset) {
+        sDataset = dataset;
+        sResponse = null;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.single_button_activity);
+
+        findViewById(R.id.button).setOnClickListener((v) -> {
+            AssistStructure structure = getIntent().getParcelableExtra(
+                    AutofillManager.EXTRA_ASSIST_STRUCTURE);
+            if (structure != null) {
+                Parcelable result;
+                if (sResponse != null) {
+                    result = sResponse.asFillResponse(/* contexts= */ null,
+                            (id) -> Helper.findNodeByResourceId(structure, id));
+                } else if (sDataset != null) {
+                    result = sDataset.asDataset(
+                            (id) -> Helper.findNodeByResourceId(structure, id));
+                } else {
+                    throw new IllegalStateException("no dataset or response");
+                }
+
+                // Pass on the auth result
+                Intent intent = new Intent();
+                intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, result);
+                setResult(RESULT_OK, intent);
+            }
+
+            // Done
+            finish();
+        });
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java
new file mode 100644
index 0000000..602efd2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java
@@ -0,0 +1,64 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import android.autofillservice.cts.testcore.Timeouts;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Empty activity that allows to be put in different split window.
+ */
+public class MultiWindowEmptyActivity extends EmptyActivity {
+
+    private static MultiWindowEmptyActivity sLastInstance;
+    private static CountDownLatch sLastInstanceLatch;
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        sLastInstance = this;
+        if (sLastInstanceLatch != null) {
+            sLastInstanceLatch.countDown();
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        if (hasFocus) {
+            if (sLastInstanceLatch != null) {
+                sLastInstanceLatch.countDown();
+            }
+        }
+    }
+
+    public static void expectNewInstance(boolean waitWindowFocus) {
+        sLastInstanceLatch = new CountDownLatch(waitWindowFocus ? 2 : 1);
+    }
+
+    public static MultiWindowEmptyActivity waitNewInstance() throws InterruptedException {
+        if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
+                TimeUnit.MILLISECONDS)) {
+            throw new RetryableException("New MultiWindowLoginActivity didn't start",
+                    Timeouts.ACTIVITY_RESURRECTION);
+        }
+        sLastInstanceLatch = null;
+        return sLastInstance;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowLoginActivity.java
new file mode 100644
index 0000000..53bd825
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowLoginActivity.java
@@ -0,0 +1,64 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import android.autofillservice.cts.testcore.Timeouts;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity that allows capture recreated instance for testing multi window scenarios.
+ */
+public class MultiWindowLoginActivity extends LoginActivity {
+
+    private static MultiWindowLoginActivity sLastInstance;
+    private static CountDownLatch sLastInstanceLatch;
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        sLastInstance = this;
+        if (sLastInstanceLatch != null) {
+            sLastInstanceLatch.countDown();
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        if (hasFocus) {
+            if (sLastInstanceLatch != null) {
+                sLastInstanceLatch.countDown();
+            }
+        }
+    }
+
+    public static void expectNewInstance(boolean waitWindowFocus) {
+        sLastInstanceLatch = new CountDownLatch(waitWindowFocus ? 2 : 1);
+    }
+
+    public static MultiWindowLoginActivity waitNewInstance() throws InterruptedException {
+        if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
+                TimeUnit.MILLISECONDS)) {
+            throw new RetryableException("New MultiWindowLoginActivity didn't start",
+                    Timeouts.ACTIVITY_RESURRECTION);
+        }
+        sLastInstanceLatch = null;
+        return sLastInstance;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/MyWebView.java b/tests/autofillservice/src/android/autofillservice/cts/activities/MyWebView.java
new file mode 100644
index 0000000..5900a9d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/MyWebView.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebView;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link WebView} used to assert contents were autofilled.
+ */
+public class MyWebView extends WebView {
+
+    private static final String TAG = "MyWebView";
+
+    private FillExpectation mExpectation;
+
+    public MyWebView(Context context) {
+        super(context);
+        setJsHandler();
+        Log.d(TAG, "isAutofillEnabled() on constructor? " + isAutofillEnabled());
+    }
+
+    public MyWebView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setJsHandler();
+        Log.d(TAG, "isAutofillEnabled() on constructor? " + isAutofillEnabled());
+    }
+
+    public void expectAutofill(String username, String password) {
+        mExpectation = new FillExpectation(username, password);
+    }
+
+    public void assertAutofilled() throws Exception {
+        assertWithMessage("expectAutofill() not called").that(mExpectation).isNotNull();
+        mExpectation.assertUsernameCalled();
+        mExpectation.assertPasswordCalled();
+    }
+
+    private void setJsHandler() {
+        getSettings().setJavaScriptEnabled(true);
+        addJavascriptInterface(new JavascriptHandler(), "JsHandler");
+    }
+
+    public boolean isAutofillEnabled() {
+        return getContext().getSystemService(AutofillManager.class).isEnabled();
+    }
+
+    private class FillExpectation {
+        private final CountDownLatch mUsernameLatch = new CountDownLatch(1);
+        private final CountDownLatch mPasswordLatch = new CountDownLatch(1);
+        private final String mExpectedUsername;
+        private final String mExpectedPassword;
+        private String mActualUsername;
+        private String mActualPassword;
+
+        FillExpectation(String username, String password) {
+            this.mExpectedUsername = username;
+            this.mExpectedPassword = password;
+        }
+
+        void setUsername(String username) {
+            mActualUsername = username;
+            mUsernameLatch.countDown();
+        }
+
+        void setPassword(String password) {
+            mActualPassword = password;
+            mPasswordLatch.countDown();
+        }
+
+        void assertUsernameCalled() throws Exception {
+            assertCalled(mUsernameLatch, "username");
+            assertWithMessage("Wrong value for username").that(mExpectation.mActualUsername)
+                .isEqualTo(mExpectation.mExpectedUsername);
+        }
+
+        void assertPasswordCalled() throws Exception {
+            assertCalled(mPasswordLatch, "password");
+            assertWithMessage("Wrong value for password").that(mExpectation.mActualPassword)
+                    .isEqualTo(mExpectation.mExpectedPassword);
+        }
+
+        private void assertCalled(CountDownLatch latch, String field) throws Exception {
+            if (!latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+                throw new RetryableException(FILL_TIMEOUT, "%s not called", field);
+            }
+        }
+    }
+
+    private class JavascriptHandler {
+
+        @JavascriptInterface
+        public void onUsernameChanged(String username) {
+            Log.d(TAG, "onUsernameChanged():" + username);
+            if (mExpectation != null) {
+                mExpectation.setUsername(username);
+            }
+        }
+
+        @JavascriptInterface
+        public void onPasswordChanged(String password) {
+            Log.d(TAG, "onPasswordChanged():" + password);
+            if (mExpectation != null) {
+                mExpectation.setPassword(password);
+            }
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/NonAutofillableActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/NonAutofillableActivity.java
new file mode 100644
index 0000000..3ece6be
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/NonAutofillableActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+
+public final class NonAutofillableActivity extends AbstractAutoFillActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.non_autofillable_activity);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/OnCreateServiceStatusVerifierActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/OnCreateServiceStatusVerifierActivity.java
new file mode 100644
index 0000000..e53d68b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/OnCreateServiceStatusVerifierActivity.java
@@ -0,0 +1,64 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Helper.getAutofillServiceName;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+
+// TODO(b/159304958): Move to activity folder
+/**
+ * Activity used to verify whether the service is enable or not when it's launched.
+ */
+public class OnCreateServiceStatusVerifierActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "OnCreateServiceStatusVerifierActivity";
+
+    public static final String SERVICE_NAME =
+            android.autofillservice.cts.testcore.NoOpAutofillService.SERVICE_NAME;
+
+    private String mSettingsOnCreate;
+    private boolean mEnabledOnCreate;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_save_activity);
+
+        mSettingsOnCreate = getAutofillServiceName();
+        mEnabledOnCreate = getAutofillManager().isEnabled();
+        Log.i(TAG, "On create: settings=" + mSettingsOnCreate + ", enabled=" + mEnabledOnCreate);
+    }
+
+    public void assertServiceStatusOnCreate(boolean enabled) {
+        if (enabled) {
+            assertWithMessage("Wrong settings").that(mSettingsOnCreate)
+                .isEqualTo(SERVICE_NAME);
+            assertWithMessage("AutofillManager.isEnabled() is wrong").that(mEnabledOnCreate)
+                .isTrue();
+
+        } else {
+            assertWithMessage("Wrong settings").that(mSettingsOnCreate).isNull();
+            assertWithMessage("AutofillManager.isEnabled() is wrong").that(mEnabledOnCreate)
+                .isFalse();
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java
new file mode 100644
index 0000000..22587d2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Activity that has the following fields:
+ *
+ * <ul>
+ *   <li>Address 1 EditText (id: address1)
+ *   <li>Address 2 EditText (id: address2)
+ *   <li>City EditText (id: city)
+ *   <li>Favorite Color EditText (id: favorite_color)
+ *   <li>Clear Button
+ *   <li>SaveButton
+ * </ul>
+ *
+ * <p>It's used to test auto-fill Save when not all fields are required.
+ */
+public class OptionalSaveActivity extends AbstractAutoFillActivity {
+
+    public static final String ID_ADDRESS1 = "address1";
+    public static final String ID_ADDRESS2 = "address2";
+    public static final String ID_CITY = "city";
+    public static final String ID_FAVORITE_COLOR = "favorite_color";
+
+    public EditText mAddress1;
+    public EditText mAddress2;
+    public EditText mCity;
+    public EditText mFavoriteColor;
+    private Button mSaveButton;
+    private Button mClearButton;
+    private FillExpectation mExpectation;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.optional_save_activity);
+
+        mAddress1 = (EditText) findViewById(R.id.address1);
+        mAddress2 = (EditText) findViewById(R.id.address2);
+        mCity = (EditText) findViewById(R.id.city);
+        mFavoriteColor = (EditText) findViewById(R.id.favorite_color);
+        mSaveButton = (Button) findViewById(R.id.save);
+        mClearButton = (Button) findViewById(R.id.clear);
+        mSaveButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                save();
+            }
+        });
+        mClearButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                resetFields();
+            }
+        });
+    }
+
+    /**
+     * Resets the values of the input fields.
+     */
+    private void resetFields() {
+        mAddress1.setText("");
+        mAddress2.setText("");
+        mCity.setText("");
+        mFavoriteColor.setText("");
+    }
+
+    /**
+     * Emulates a save action.
+     */
+    public void save() {
+        final Intent intent = new Intent(this, WelcomeActivity.class);
+        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Saved and sounded, please come again!");
+
+        startActivity(intent);
+        finish();
+    }
+
+    /**
+     * Sets the expectation for an auto-fill request, so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(@Nullable String address1, @Nullable String address2,
+            @Nullable String city,
+            @Nullable String favColor) {
+        mExpectation = new FillExpectation(address1, address2, city, favColor);
+        if (address1 != null) {
+            mAddress1.addTextChangedListener(mExpectation.address1Watcher);
+        }
+        if (address2 != null) {
+            mAddress2.addTextChangedListener(mExpectation.address2Watcher);
+        }
+        if (city != null) {
+            mCity.addTextChangedListener(mExpectation.cityWatcher);
+        }
+        if (favColor != null) {
+            mFavoriteColor.addTextChangedListener(mExpectation.favoriteColorWatcher);
+        }
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, String, String, String)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        if (mExpectation.address1Watcher != null) {
+            mExpectation.address1Watcher.assertAutoFilled();
+        }
+        if (mExpectation.address2Watcher != null) {
+            mExpectation.address2Watcher.assertAutoFilled();
+        }
+        if (mExpectation.cityWatcher != null) {
+            mExpectation.cityWatcher.assertAutoFilled();
+        }
+        if (mExpectation.favoriteColorWatcher != null) {
+            mExpectation.favoriteColorWatcher.assertAutoFilled();
+        }
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final OneTimeTextWatcher address1Watcher;
+        private final OneTimeTextWatcher address2Watcher;
+        private final OneTimeTextWatcher cityWatcher;
+        private final OneTimeTextWatcher favoriteColorWatcher;
+
+        private FillExpectation(@Nullable String address1, @Nullable String address2,
+                @Nullable String city, @Nullable String favColor) {
+            address1Watcher = address1 == null ? null
+                    : new OneTimeTextWatcher("address1", mAddress1, address1);
+            address2Watcher = address2 == null ? null
+                    : new OneTimeTextWatcher("address2", mAddress2, address2);
+            cityWatcher = city == null ? null : new OneTimeTextWatcher("city", mCity, city);
+            favoriteColorWatcher = favColor == null ? null
+                    : new OneTimeTextWatcher("favColor", mFavoriteColor, favColor);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/OutOfProcessLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/OutOfProcessLoginActivity.java
new file mode 100644
index 0000000..4cab12c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/OutOfProcessLoginActivity.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Activity;
+import android.autofillservice.cts.R;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Simple activity showing R.layout.login_activity. Started outside of the test process.
+ */
+public class OutOfProcessLoginActivity extends Activity {
+    private static final String TAG = "OutOfProcessLoginActivity";
+
+    private static OutOfProcessLoginActivity sInstance;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate(" + savedInstanceState + ")");
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.login_activity);
+
+        findViewById(R.id.login).setOnClickListener((v) -> finish());
+
+        sInstance = this;
+    }
+
+    @Override
+    protected void onStart() {
+        Log.i(TAG, "onStart()");
+        super.onStart();
+        try {
+            if (!getStartedMarker(this).createNewFile()) {
+                Log.e(TAG, "cannot write started file");
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "cannot write started file: " + e);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        Log.i(TAG, "onStop()");
+        super.onStop();
+
+        try {
+            if (!getStoppedMarker(this).createNewFile()) {
+                Log.e(TAG, "could not write stopped marker");
+            } else {
+                Log.v(TAG, "wrote stopped marker");
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "could write stopped marker: " + e);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.i(TAG, "onDestroy()");
+        try {
+            if (!getDestroyedMarker(this).createNewFile()) {
+                Log.e(TAG, "could not write destroyed marker");
+            } else {
+                Log.v(TAG, "wrote destroyed marker");
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "could write destroyed marker: " + e);
+        }
+        super.onDestroy();
+        sInstance = null;
+    }
+
+    /**
+     * Get the file that signals that the activity has entered {@link Activity#onStop()}.
+     *
+     * @param context Context of the app
+     * @return The marker file that is written onStop()
+     */
+    @NonNull public static File getStoppedMarker(@NonNull Context context) {
+        return new File(context.getFilesDir(), "stopped");
+    }
+
+    /**
+     * Get the file that signals that the activity has entered {@link Activity#onStart()}.
+     *
+     * @param context Context of the app
+     * @return The marker file that is written onStart()
+     */
+    @NonNull public static File getStartedMarker(@NonNull Context context) {
+        return new File(context.getFilesDir(), "started");
+    }
+
+   /**
+     * Get the file that signals that the activity has entered {@link Activity#onDestroy()}.
+     *
+     * @param context Context of the app
+     * @return The marker file that is written onDestroy()
+     */
+    @NonNull public static File getDestroyedMarker(@NonNull Context context) {
+        return new File(context.getFilesDir(), "destroyed");
+    }
+
+    public static void finishIt() {
+        Log.v(TAG, "Finishing " + sInstance);
+        if (sInstance != null) {
+            sInstance.finish();
+        }
+    }
+
+    public static boolean hasInstance() {
+        return sInstance != null;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/PasswordOnlyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/PasswordOnlyActivity.java
new file mode 100644
index 0000000..8a1d307
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/PasswordOnlyActivity.java
@@ -0,0 +1,76 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public final class PasswordOnlyActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "PasswordOnlyActivity";
+
+    static final String EXTRA_USERNAME = "username";
+    static final String EXTRA_PASSWORD_AUTOFILL_ID = "password_autofill_id";
+
+    private TextView mWelcomeLabel;
+    private EditText mPasswordEditText;
+    private Button mLoginButton;
+    private String mUsername;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(getContentView());
+
+        mWelcomeLabel = findViewById(R.id.welcome);
+        mPasswordEditText = findViewById(R.id.password);
+        mLoginButton = findViewById(R.id.login);
+        mLoginButton.setOnClickListener((v) -> login());
+
+        mUsername = getIntent().getStringExtra(EXTRA_USERNAME);
+        final String welcomeMsg = "Welcome to the jungle, " + mUsername;
+        Log.v(TAG, welcomeMsg);
+        mWelcomeLabel.setText(welcomeMsg);
+        final AutofillId id = getIntent().getParcelableExtra(EXTRA_PASSWORD_AUTOFILL_ID);
+        if (id != null) {
+            Log.v(TAG, "Setting autofill id to " + id);
+            mPasswordEditText.setAutofillId(id);
+        }
+    }
+
+    protected int getContentView() {
+        return R.layout.password_only_activity;
+    }
+
+    public void focusOnPassword() {
+        syncRunOnUiThread(() -> mPasswordEditText.requestFocus());
+    }
+
+    public void setPassword(String password) {
+        syncRunOnUiThread(() -> mPasswordEditText.setText(password));
+    }
+
+    public void login() {
+        final String password = mPasswordEditText.getText().toString();
+        Log.i(TAG, "Login as " + mUsername + "/" + password);
+        finish();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/PreFilledLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/PreFilledLoginActivity.java
new file mode 100644
index 0000000..917cc4b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/PreFilledLoginActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+/**
+ * Same as {@link LoginActivity}, but with {@code username} and {@code password} fields pre-filled.
+ */
+public class PreFilledLoginActivity extends LoginActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.pre_filled_login_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java
new file mode 100644
index 0000000..02881cb
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+/**
+ * A simple activity that upon submission launches {@link SimpleSaveActivity}.
+ */
+public class PreSimpleSaveActivity extends AbstractAutoFillActivity {
+
+    public static final String ID_PRE_LABEL = "preLabel";
+    public static final String ID_PRE_INPUT = "preInput";
+
+    private static PreSimpleSaveActivity sInstance;
+
+    public TextView mPreLabel;
+    public EditText mPreInput;
+    public Button mSubmit;
+
+    public static PreSimpleSaveActivity getInstance() {
+        return sInstance;
+    }
+
+    public PreSimpleSaveActivity() {
+        sInstance = this;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.pre_simple_save_activity);
+
+        mPreLabel = findViewById(R.id.preLabel);
+        mPreInput = findViewById(R.id.preInput);
+        mSubmit = findViewById(R.id.submit);
+
+        mSubmit.setOnClickListener((v) -> {
+            finish();
+            startActivity(new Intent(this, SimpleSaveActivity.class));
+        });
+    }
+
+    public FillExpectation expectAutoFill(String input) {
+        final FillExpectation expectation = new FillExpectation(input);
+        mPreInput.addTextChangedListener(expectation.mInputWatcher);
+        return expectation;
+    }
+
+    public EditText getPreInput() {
+        return mPreInput;
+    }
+
+    public final class FillExpectation {
+        private final OneTimeTextWatcher mInputWatcher;
+
+        private FillExpectation(String input) {
+            mInputWatcher = new OneTimeTextWatcher("input", mPreInput, input);
+        }
+
+        public void assertAutoFilled() throws Exception {
+            mInputWatcher.assertAutoFilled();
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/SecondActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/SecondActivity.java
new file mode 100644
index 0000000..71ff7ed
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/SecondActivity.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 android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.UiBot;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.widget.TextView;
+
+/**
+ * Activity that is used to test restored mechanism will work while running below steps:
+ * 1. Taps span on the save UI to start the ViewActionActivity.
+ * 2. Launches the SecondActivity and immediately finish the ViewActionActivity.
+ * 3. Presses back key on the SecondActivity.
+ * The expected that the save UI should have been restored.
+ */
+public class SecondActivity extends AbstractAutoFillActivity {
+
+    private static SecondActivity sInstance;
+
+    private static final String TAG = "SecondActivity";
+    public static final String ID_WELCOME = "welcome";
+    public static final String DEFAULT_MESSAGE = "Welcome second activity";
+
+    public SecondActivity() {
+        sInstance = this;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.welcome_activity);
+
+        TextView welcome = (TextView) findViewById(R.id.welcome);
+        welcome.setText(DEFAULT_MESSAGE);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Setting sInstance to null onDestroy()");
+        sInstance = null;
+    }
+
+    public static void finishIt() {
+        if (sInstance != null) {
+            sInstance.finish();
+        }
+    }
+
+    public static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
+        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
+        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
+                .isEqualTo(DEFAULT_MESSAGE);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleAfterLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleAfterLoginActivity.java
new file mode 100644
index 0000000..b247f5b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleAfterLoginActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity that displays a "Finished login activity!" message after login.
+ */
+public class SimpleAfterLoginActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "SimpleAfterLoginActivity";
+
+    public static final String ID_AFTER_LOGIN = "after_login";
+
+    private static SimpleAfterLoginActivity sCurrentActivity;
+
+    public static SimpleAfterLoginActivity getCurrentActivity() {
+        return sCurrentActivity;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_after_login_activity);
+
+        Log.v(TAG, "Set sCurrentActivity to this onCreate()");
+        sCurrentActivity = this;
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
+        sCurrentActivity = null;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleBeforeLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleBeforeLoginActivity.java
new file mode 100644
index 0000000..3341adf
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleBeforeLoginActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity that displays a "Launch login activity!" message before login.
+ */
+public class SimpleBeforeLoginActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "SimpleBeforeLoginActivity";
+
+    public static final String ID_BEFORE_LOGIN = "before_login";
+
+    private static SimpleBeforeLoginActivity sCurrentActivity;
+
+    public static SimpleBeforeLoginActivity getCurrentActivity() {
+        return sCurrentActivity;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_before_login_activity);
+
+        Log.v(TAG, "Set sCurrentActivity to this onCreate()");
+        sCurrentActivity = this;
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
+        sCurrentActivity = null;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java
new file mode 100644
index 0000000..1ba0b07
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+/**
+ * Simple activity that has an edit text and buttons to cancel or commit the autofill context.
+ */
+public class SimpleSaveActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "SimpleSaveActivity";
+
+    public static final String ID_LABEL = "label";
+    public static final String ID_INPUT = "input";
+    public static final String ID_PASSWORD = "password";
+    public static final String ID_COMMIT = "commit";
+    public static final String TEXT_LABEL = "Label:";
+
+    private static SimpleSaveActivity sInstance;
+
+    public TextView mLabel;
+    public EditText mInput;
+    public EditText mPassword;
+    public Button mCancel;
+    public Button mCommit;
+
+    private boolean mAutoCommit = true;
+    private boolean mClearFieldsOnSubmit = false;
+
+    public static SimpleSaveActivity getInstance() {
+        return sInstance;
+    }
+
+    public SimpleSaveActivity() {
+        sInstance = this;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_save_activity);
+
+        mLabel = findViewById(R.id.label);
+        mInput = findViewById(R.id.input);
+        mPassword = findViewById(R.id.password);
+        mCancel = findViewById(R.id.cancel);
+        mCommit = findViewById(R.id.commit);
+
+        mCancel.setOnClickListener((v) -> getAutofillManager().cancel());
+        mCommit.setOnClickListener((v) -> onCommit());
+    }
+
+    private void onCommit() {
+        if (mClearFieldsOnSubmit) {
+            resetFields();
+        }
+        if (mAutoCommit) {
+            Log.d(TAG, "onCommit(): calling AFM.commit()");
+            getAutofillManager().commit();
+        } else {
+            Log.d(TAG, "onCommit(): NOT calling AFM.commit()");
+        }
+    }
+
+    private void resetFields() {
+        Log.d(TAG, "resetFields()");
+        mInput.setText("");
+        mPassword.setText("");
+    }
+
+    /**
+     * Defines whether the activity should automatically call {@link AutofillManager#commit()} when
+     * the commit button is tapped.
+     */
+    public void setAutoCommit(boolean flag) {
+        mAutoCommit = flag;
+    }
+
+    /**
+     * Defines whether the activity should automatically clear its fields when submit is clicked.
+     */
+    public void setClearFieldsOnSubmit(boolean flag) {
+        mClearFieldsOnSubmit = flag;
+    }
+
+    public FillExpectation expectAutoFill(String input) {
+        final FillExpectation expectation = new FillExpectation(input, null);
+        mInput.addTextChangedListener(expectation.mInputWatcher);
+        return expectation;
+    }
+
+    public FillExpectation expectAutoFill(String input, String password) {
+        final FillExpectation expectation = new FillExpectation(input, password);
+        mInput.addTextChangedListener(expectation.mInputWatcher);
+        mPassword.addTextChangedListener(expectation.mPasswordWatcher);
+        return expectation;
+    }
+
+    public EditText getInput() {
+        return mInput;
+    }
+
+    public final class FillExpectation {
+        private final OneTimeTextWatcher mInputWatcher;
+        private final OneTimeTextWatcher mPasswordWatcher;
+
+        private FillExpectation(String input, String password) {
+            mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
+            mPasswordWatcher = password == null
+                    ? null
+                    : new OneTimeTextWatcher("password", mPassword, password);
+        }
+
+        public void assertAutoFilled() throws Exception {
+            mInputWatcher.assertAutoFilled();
+            if (mPasswordWatcher != null) {
+                mPasswordWatcher.assertAutoFilled();
+            }
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerClockActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerClockActivity.java
new file mode 100644
index 0000000..46a1b67
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerClockActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+public class TimePickerClockActivity extends AbstractTimePickerActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.time_picker_clock_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerSpinnerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerSpinnerActivity.java
new file mode 100644
index 0000000..d4ad195
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerSpinnerActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+public class TimePickerSpinnerActivity extends AbstractTimePickerActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.time_picker_spinner_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineForResultActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineForResultActivity.java
new file mode 100644
index 0000000..e11a723
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineForResultActivity.java
@@ -0,0 +1,67 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Intent;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity used to launch another activity for result.
+ */
+// TODO: move to common code
+public class TrampolineForResultActivity extends AbstractAutoFillActivity {
+    private static final String TAG = "TrampolineForResultActivity";
+
+    private final CountDownLatch mLatch = new CountDownLatch(1);
+
+    private int mExpectedRequestCode;
+    private int mActualRequestCode;
+    private int mActualResultCode;
+
+    /**
+     * Starts an activity for result.
+     */
+    public void startForResult(Intent intent, int requestCode) {
+        mExpectedRequestCode = requestCode;
+        startActivityForResult(intent, requestCode);
+    }
+
+    /**
+     * Asserts the activity launched by {@link #startForResult(Intent, int)} was finished with the
+     * expected result code, or fails if it times out.
+     */
+    public void assertResult(int expectedResultCode) throws Exception {
+        final boolean called = mLatch.await(1000, TimeUnit.MILLISECONDS);
+        assertWithMessage("Result not received in 1s").that(called).isTrue();
+        assertWithMessage("Wrong actual code").that(mActualRequestCode)
+            .isEqualTo(mExpectedRequestCode);
+        assertWithMessage("Wrong result code").that(mActualResultCode)
+                .isEqualTo(expectedResultCode);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        Log.d(TAG, "onActivityResult(): req=" + requestCode + ", res=" + resultCode);
+        mActualRequestCode = requestCode;
+        mActualResultCode = resultCode;
+        mLatch.countDown();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineWelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineWelcomeActivity.java
new file mode 100644
index 0000000..0861c99
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineWelcomeActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Activity that launches a new {@link WelcomeActivity} and finishes right away.
+ */
+public class TrampolineWelcomeActivity extends AbstractAutoFillActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        startActivity(new Intent(this, WelcomeActivity.class));
+        finish();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/UsernameOnlyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/UsernameOnlyActivity.java
new file mode 100644
index 0000000..d98633c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/UsernameOnlyActivity.java
@@ -0,0 +1,77 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.widget.Button;
+import android.widget.EditText;
+
+public final class UsernameOnlyActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "UsernameOnlyActivity";
+
+    private EditText mUsernameEditText;
+    private Button mNextButton;
+    private AutofillId mPasswordAutofillId;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(getContentView());
+
+        mUsernameEditText = findViewById(R.id.username);
+        mNextButton = findViewById(R.id.next);
+        mNextButton.setOnClickListener((v) -> next());
+    }
+
+    protected int getContentView() {
+        return R.layout.username_only_activity;
+    }
+
+    public void focusOnUsername() {
+        syncRunOnUiThread(() -> mUsernameEditText.requestFocus());
+    }
+
+    public void setUsername(String username) {
+        syncRunOnUiThread(() -> mUsernameEditText.setText(username));
+    }
+
+    public AutofillId getUsernameAutofillId() {
+        return mUsernameEditText.getAutofillId();
+    }
+
+    /**
+     * Sets the autofill id of the password using the intent that launches the new activity, so it's
+     * set before the view strucutre is generated.
+     */
+    public void setPasswordAutofillId(AutofillId id) {
+        mPasswordAutofillId = id;
+    }
+
+    public void next() {
+        final String username = mUsernameEditText.getText().toString();
+        Log.v(TAG, "Going to next screen as user " + username + " and aid " + mPasswordAutofillId);
+        final Intent intent = new Intent(this, PasswordOnlyActivity.class)
+                .putExtra(PasswordOnlyActivity.EXTRA_USERNAME, username)
+                .putExtra(PasswordOnlyActivity.EXTRA_PASSWORD_AUTOFILL_ID, mPasswordAutofillId);
+        startActivity(intent);
+        finish();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/ViewActionActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/ViewActionActivity.java
new file mode 100644
index 0000000..29d853a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/ViewActionActivity.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.widget.TextView;
+
+/**
+ * Activity that handles VIEW action.
+ */
+public class ViewActionActivity extends AbstractAutoFillActivity {
+
+    private static ViewActionActivity sInstance;
+
+    private static final String TAG = "ViewActionHandleActivity";
+    static final String ID_WELCOME = "welcome";
+    static final String DEFAULT_MESSAGE = "Welcome VIEW action handle activity";
+    private boolean mHasCustomBackBehavior;
+
+    public enum ActivityCustomAction {
+        NORMAL_ACTIVITY,
+        FAST_FORWARD_ANOTHER_ACTIVITY,
+        TAP_BACK_WITHOUT_FINISH
+    }
+
+    public ViewActionActivity() {
+        sInstance = this;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.welcome_activity);
+
+        final Uri data = getIntent().getData();
+        ActivityCustomAction type = ActivityCustomAction.valueOf(data.getSchemeSpecificPart());
+
+        switch (type) {
+            case FAST_FORWARD_ANOTHER_ACTIVITY:
+                startSecondActivity();
+                break;
+            case TAP_BACK_WITHOUT_FINISH:
+                mHasCustomBackBehavior = true;
+                break;
+            case NORMAL_ACTIVITY:
+            default:
+                // no-op
+        }
+
+        TextView welcome = (TextView) findViewById(R.id.welcome);
+        welcome.setText(DEFAULT_MESSAGE);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Setting sInstance to null onDestroy()");
+        sInstance = null;
+    }
+
+    @Override
+    public void finish() {
+        super.finish();
+        mHasCustomBackBehavior = false;
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (mHasCustomBackBehavior) {
+            moveTaskToBack(true);
+            return;
+        }
+        super.onBackPressed();
+    }
+
+    public static void finishIt() {
+        if (sInstance != null) {
+            sInstance.finish();
+        }
+    }
+
+    private void startSecondActivity() {
+        final Intent intent = new Intent(this, SecondActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+        startActivity(intent);
+        finish();
+    }
+
+    public static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
+        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
+        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
+                .isEqualTo(DEFAULT_MESSAGE);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/ViewAttributesTestActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/ViewAttributesTestActivity.java
new file mode 100644
index 0000000..0fa4f94
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/ViewAttributesTestActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+public class ViewAttributesTestActivity extends AbstractAutoFillActivity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.view_attribute_test_activity);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerActivity.java
new file mode 100644
index 0000000..b4ea508
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerActivity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.VirtualContainerView.Line;
+import android.autofillservice.cts.activities.VirtualContainerView.Line.OneTimeLineWatcher;
+import android.graphics.Canvas;
+import android.os.Bundle;
+import android.text.InputType;
+import android.widget.EditText;
+
+/**
+ * A custom activity that uses {@link Canvas} to draw the following fields:
+ *
+ * <ul>
+ *   <li>Username
+ *   <li>Password
+ * </ul>
+ */
+public class VirtualContainerActivity extends AbstractAutoFillActivity {
+
+    public static final String BLANK_VALUE = "        ";
+    public static final String INITIAL_URL_BAR_VALUE = "ftp://dev.null/4/8/15/16/23/42";
+
+    public EditText mUrlBar;
+    public EditText mUrlBar2;
+    public VirtualContainerView mCustomView;
+
+    public Line mUsername;
+    public Line mPassword;
+
+    private FillExpectation mExpectation;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.virtual_container_activity);
+
+        mUrlBar = findViewById(R.id.my_url_bar);
+        mUrlBar2 = findViewById(R.id.my_url_bar2);
+        mCustomView = findViewById(R.id.virtual_container_view);
+
+        mUrlBar.setText(INITIAL_URL_BAR_VALUE);
+        mUsername = mCustomView.addLine(ID_USERNAME_LABEL, "Username", ID_USERNAME, BLANK_VALUE,
+                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL);
+        mPassword = mCustomView.addLine(ID_PASSWORD_LABEL, "Password", ID_PASSWORD, BLANK_VALUE,
+                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+    }
+
+    /**
+     * Triggers manual autofill in a given line.
+     */
+    public void requestAutofill(Line line) {
+        getAutofillManager().requestAutofill(mCustomView, line.text.id, line.bounds);
+    }
+
+    /**
+     * Sets the expectation for an auto-fill request, so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(String username, String password) {
+        mExpectation = new FillExpectation(username, password);
+        mUsername.setTextChangedListener(mExpectation.ccUsernameWatcher);
+        mPassword.setTextChangedListener(mExpectation.ccPasswordWatcher);
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, String)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        mExpectation.ccUsernameWatcher.assertAutoFilled();
+        mExpectation.ccPasswordWatcher.assertAutoFilled();
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final OneTimeLineWatcher ccUsernameWatcher;
+        private final OneTimeLineWatcher ccPasswordWatcher;
+
+        private FillExpectation(String username, String password) {
+            ccUsernameWatcher = mUsername.new OneTimeLineWatcher(username);
+            ccPasswordWatcher = mPassword.new OneTimeLineWatcher(password);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerView.java b/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerView.java
new file mode 100644
index 0000000..bb3d37d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerView.java
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.ViewStructure.HtmlInfo;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class VirtualContainerView extends View {
+
+    private static final String TAG = "VirtualContainerView";
+    private static final int LOGIN_BUTTON_VIRTUAL_ID = 666;
+
+    public static final String LABEL_CLASS = "my.readonly.view";
+    public static final String TEXT_CLASS = "my.editable.view";
+    public static final String ID_URL_BAR = "my_url_bar";
+    public static final String ID_URL_BAR2 = "my_url_bar2";
+
+    public final AutofillId mLoginButtonId;
+    private final ArrayList<Line> mLines = new ArrayList<>();
+    private final SparseArray<Item> mItems = new SparseArray<>();
+    private AutofillManager mAfm;
+
+    private Line mFocusedLine;
+    private int mNextChildId;
+
+    private Paint mTextPaint;
+    private int mTextHeight;
+    private int mTopMargin;
+    private int mLeftMargin;
+    private int mVerticalGap;
+    private int mLineLength;
+    private int mFocusedColor;
+    private int mUnfocusedColor;
+    private boolean mSync = true;
+    private boolean mOverrideDispatchProvideAutofillStructure = false;
+
+    private boolean mCompatMode = false;
+    private AccessibilityDelegate mAccessibilityDelegate;
+    private AccessibilityNodeProvider mAccessibilityNodeProvider;
+
+    /**
+     * Enum defining how the view communicate visibility changes to the framework
+     */
+    public enum VisibilityIntegrationMode {
+        NOTIFY_AFM,
+        OVERRIDE_IS_VISIBLE_TO_USER
+    }
+
+    private VisibilityIntegrationMode mVisibilityIntegrationMode;
+
+    public VirtualContainerView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        setAutofillManager(context);
+
+        mTextPaint = new Paint();
+
+        mUnfocusedColor = Color.BLACK;
+        mFocusedColor = Color.RED;
+        mTextPaint.setStyle(Style.FILL);
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getMetrics(metrics);
+        mTopMargin = metrics.heightPixels * 3 / 100;
+        mLeftMargin = metrics.widthPixels * 3 / 100;
+        mTextHeight = metrics.widthPixels * 3 / 100; // adjust text size with display width
+        mVerticalGap = metrics.heightPixels / 100;
+
+        mLineLength = mTextHeight + mVerticalGap;
+        mTextPaint.setTextSize(mTextHeight);
+        Log.d(TAG, "Text height: " + mTextHeight);
+        mLoginButtonId = new AutofillId(getAutofillId(), LOGIN_BUTTON_VIRTUAL_ID);
+    }
+
+    public void setAutofillManager(Context context) {
+        mAfm = context.getSystemService(AutofillManager.class);
+        Log.d(TAG, "Set AFM from " + context);
+    }
+
+    @Override
+    public void autofill(SparseArray<AutofillValue> values) {
+        Log.d(TAG, "autofill: " + values);
+        if (mCompatMode) {
+            Log.v(TAG, "using super.autofill() on compat mode");
+            super.autofill(values);
+            return;
+        }
+        for (int i = 0; i < values.size(); i++) {
+            final int id = values.keyAt(i);
+            final AutofillValue value = values.valueAt(i);
+            final Item item = getItem(id);
+            item.autofill(value.getTextValue());
+        }
+        postInvalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        Log.d(TAG, "onDraw: " + mLines.size() + " lines; canvas:" + canvas);
+        float x;
+        float y = mTopMargin + mLineLength;
+        for (int i = 0; i < mLines.size(); i++) {
+            x = mLeftMargin;
+            final Line line = mLines.get(i);
+            if (!line.visible) {
+                continue;
+            }
+            Log.v(TAG, "Drawing '" + line + "' at " + x + "x" + y);
+            mTextPaint.setColor(line.focused ? mFocusedColor : mUnfocusedColor);
+            final String readOnlyText = line.label.text + ":  [";
+            final String writeText = line.text.text + "]";
+            // Paints the label first...
+            canvas.drawText(readOnlyText, x, y, mTextPaint);
+            // ...then paints the edit text and sets the proper boundary
+            final float deltaX = mTextPaint.measureText(readOnlyText);
+            x += deltaX;
+            line.bounds.set((int) x, (int) (y - mLineLength),
+                    (int) (x + mTextPaint.measureText(writeText)), (int) y);
+            Log.d(TAG, "setBounds(" + x + ", " + y + "): " + line.bounds);
+            canvas.drawText(writeText, x, y, mTextPaint);
+            y += mLineLength;
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final int y = (int) event.getY();
+        Log.d(TAG, "You can touch this: y=" + y + ", range=" + mLineLength + ", top=" + mTopMargin);
+        int lowerY = mTopMargin;
+        int upperY = -1;
+        for (int i = 0; i < mLines.size(); i++) {
+            upperY = lowerY + mLineLength;
+            final Line line = mLines.get(i);
+            Log.d(TAG, "Line " + i + " ranges from " + lowerY + " to " + upperY);
+            if (lowerY <= y && y <= upperY) {
+                if (mFocusedLine != null) {
+                    Log.d(TAG, "Removing focus from " + mFocusedLine);
+                    mFocusedLine.changeFocus(false);
+                }
+                Log.d(TAG, "Changing focus to " + line);
+                mFocusedLine = line;
+                mFocusedLine.changeFocus(true);
+                invalidate();
+                break;
+            }
+            lowerY += mLineLength;
+        }
+        return super.onTouchEvent(event);
+    }
+
+    @Override
+    public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) {
+        if (mOverrideDispatchProvideAutofillStructure) {
+            Log.d(TAG, "Overriding dispatchProvideAutofillStructure()");
+            structure.setAutofillId(getAutofillId());
+            onProvideAutofillVirtualStructure(structure, flags);
+        } else {
+            super.dispatchProvideAutofillStructure(structure, flags);
+        }
+    }
+
+    @Override
+    public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
+        Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags);
+        super.onProvideAutofillVirtualStructure(structure, flags);
+
+        if (mCompatMode) {
+            Log.v(TAG, "using super.onProvideAutofillVirtualStructure() on compat mode");
+            return;
+        }
+
+        final String packageName = getContext().getPackageName();
+        structure.setClassName(getClass().getName());
+        final int childrenSize = mItems.size();
+        int index = structure.addChildCount(childrenSize);
+        final String syncMsg = mSync ? "" : " (async)";
+        for (int i = 0; i < childrenSize; i++) {
+            final Item item = mItems.valueAt(i);
+            Log.d(TAG, "Adding new child" + syncMsg + " at index " + index + ": " + item);
+            final ViewStructure child = mSync
+                    ? structure.newChild(index)
+                    : structure.asyncNewChild(index);
+            child.setAutofillId(structure.getAutofillId(), item.id);
+            child.setDataIsSensitive(item.sensitive);
+            if (item.editable) {
+                child.setInputType(item.line.inputType);
+            }
+            index++;
+            child.setClassName(item.className);
+            // Must set "fake" idEntry because that's what the test cases use to find nodes.
+            child.setId(1000 + index, packageName, "id", item.resourceId);
+            child.setText(item.text);
+            if (TextUtils.getTrimmedLength(item.text) > 0) {
+                // TODO: Must checked trimmed length because input fields use 8 empty spaces to
+                // set width
+                child.setAutofillValue(AutofillValue.forText(item.text));
+            }
+            child.setFocused(item.line.focused);
+            child.setHtmlInfo(child.newHtmlInfoBuilder("TAGGY")
+                    .addAttribute("a1", "v1")
+                    .addAttribute("a2", "v2")
+                    .addAttribute("a1", "v2")
+                    .build());
+            child.setAutofillHints(new String[] {"c", "a", "a", "b", "a", "a"});
+
+            if (!mSync) {
+                Log.d(TAG, "Commiting virtual child");
+                child.asyncCommit();
+            }
+        }
+    }
+
+    @Override
+    public boolean isVisibleToUserForAutofill(int virtualId) {
+        boolean callSuper = true;
+        if (mVisibilityIntegrationMode == null) {
+            Log.w(TAG, "isVisibleToUserForAutofill(): mVisibilityIntegrationMode not set");
+        } else {
+            callSuper = mVisibilityIntegrationMode == VisibilityIntegrationMode.NOTIFY_AFM;
+        }
+        final boolean isVisible;
+        if (callSuper) {
+            isVisible = super.isVisibleToUserForAutofill(virtualId);
+            Log.d(TAG, "isVisibleToUserForAutofill(" + virtualId + ") using super: " + isVisible);
+        } else {
+            final Item item = getItem(virtualId);
+            isVisible = item.line.visible;
+            Log.d(TAG, "isVisibleToUserForAutofill(" + virtualId + ") set by test: " + isVisible);
+        }
+        return isVisible;
+    }
+
+    /**
+     * Emulates clicking the login button.
+     */
+    public void clickLogin() {
+        Log.d(TAG, "clickLogin()");
+        if (mCompatMode) {
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED, LOGIN_BUTTON_VIRTUAL_ID);
+        } else {
+            mAfm.notifyViewClicked(this, LOGIN_BUTTON_VIRTUAL_ID);
+        }
+    }
+
+    private Item getItem(int id) {
+        final Item item = mItems.get(id);
+        assertWithMessage("No item for id %s", id).that(item).isNotNull();
+        return item;
+    }
+
+    private AccessibilityNodeInfo onProvideAutofillCompatModeAccessibilityNodeInfo() {
+        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+
+        final String packageName = getContext().getPackageName();
+        node.setPackageName(packageName);
+        node.setClassName(getClass().getName());
+
+        final int childrenSize = mItems.size();
+        for (int i = 0; i < childrenSize; i++) {
+            final Item item = mItems.valueAt(i);
+            final int id = i + 1;
+            Log.d(TAG, "Adding new A11Y child with id " + id + ": " + item);
+
+            node.addChild(this, id);
+        }
+
+        return node;
+    }
+
+    private AccessibilityNodeInfo onProvideAutofillCompatModeAccessibilityNodeInfoForLoginButton() {
+        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+        node.setSource(this, LOGIN_BUTTON_VIRTUAL_ID);
+        node.setPackageName(getContext().getPackageName());
+        // TODO(b/37566627): ideally this button should be visible / drawn in the canvas and contain
+        // more properties like boundaries, class name, text etc...
+        return node;
+    }
+
+    public static void assertHtmlInfo(ViewNode node) {
+        final String name = node.getText().toString();
+        final HtmlInfo info = node.getHtmlInfo();
+        assertWithMessage("no HTML info on %s", name).that(info).isNotNull();
+        assertWithMessage("wrong HTML tag on %s", name).that(info.getTag()).isEqualTo("TAGGY");
+        assertWithMessage("wrong attributes on %s", name).that(info.getAttributes())
+                .containsExactly(
+                        new Pair<>("a1", "v1"),
+                        new Pair<>("a2", "v2"),
+                        new Pair<>("a1", "v2"));
+    }
+
+    public Line addLine(String labelId, String label, String textId, String text, int inputType) {
+        final Line line = new Line(labelId, label, textId, text, inputType);
+        Log.d(TAG, "addLine: " + line);
+        mLines.add(line);
+        mItems.put(line.label.id, line.label);
+        mItems.put(line.text.id, line.text);
+        return line;
+    }
+
+    public void setSync(boolean sync) {
+        mSync = sync;
+    }
+
+    public void setCompatMode(boolean compatMode) {
+        mCompatMode = compatMode;
+
+        if (mCompatMode) {
+            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+            mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
+                @Override
+                public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+                    Log.d(TAG, "createAccessibilityNodeInfo(): id=" + virtualViewId);
+                    switch (virtualViewId) {
+                        case AccessibilityNodeProvider.HOST_VIEW_ID:
+                            return onProvideAutofillCompatModeAccessibilityNodeInfo();
+                        case LOGIN_BUTTON_VIRTUAL_ID:
+                            return onProvideAutofillCompatModeAccessibilityNodeInfoForLoginButton();
+                        default:
+                            final Item item = getItem(virtualViewId);
+                            return item.provideAccessibilityNodeInfo(VirtualContainerView.this,
+                                    getContext());
+                    }
+                }
+
+                @Override
+                public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+                    if (action == AccessibilityNodeInfo.ACTION_SET_TEXT) {
+                        final CharSequence text = arguments.getCharSequence(
+                                AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
+                        final Item item = getItem(virtualViewId);
+                        item.autofill(text);
+                        return true;
+                    }
+
+                    return false;
+                }
+            };
+            mAccessibilityDelegate = new AccessibilityDelegate() {
+                @Override
+                public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+                    return mAccessibilityNodeProvider;
+                }
+            };
+
+            setAccessibilityDelegate(mAccessibilityDelegate);
+        }
+    }
+
+    public void setOverrideDispatchProvideAutofillStructure(boolean flag) {
+        mOverrideDispatchProvideAutofillStructure = flag;
+    }
+
+    private void sendAccessibilityEvent(int eventType, int virtualId) {
+        final AccessibilityEvent event = AccessibilityEvent.obtain();
+        event.setEventType(eventType);
+        event.setSource(VirtualContainerView.this, virtualId);
+        event.setEnabled(true);
+        event.setPackageName(getContext().getPackageName());
+        Log.v(TAG, "sendAccessibilityEvent(" + eventType + ", " + virtualId + "): " + event);
+        getContext().getSystemService(AccessibilityManager.class).sendAccessibilityEvent(event);
+    }
+
+    public final class Line {
+
+        public final Item text;
+        final Item label;
+        // Boundaries of the text field, relative to the CustomView
+        final Rect bounds = new Rect();
+        // Boundaries of the text field, relative to the screen
+        Rect absBounds;
+
+        private boolean focused;
+        private boolean visible = true;
+        private final int inputType;
+
+        private Line(String labelId, String label, String textId, String text, int inputType) {
+            this.label = new Item(this, ++mNextChildId, labelId, label, false, false);
+            this.text = new Item(this, ++mNextChildId, textId, text, true, true);
+            this.inputType = inputType;
+        }
+
+        public void changeFocus(boolean focused) {
+            this.focused = focused;
+
+            if (focused) {
+                absBounds = getAbsCoordinates();
+                Log.v(TAG, "Setting absBounds for " + text.id + " on focus change: " + absBounds);
+            }
+
+            if (mCompatMode) {
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED, text.id);
+                return;
+            }
+
+            if (focused) {
+                Log.d(TAG, "focus gained on " + text.id + "; absBounds=" + absBounds);
+                mAfm.notifyViewEntered(VirtualContainerView.this, text.id, absBounds);
+            } else {
+                Log.d(TAG, "focus lost on " + text.id);
+                mAfm.notifyViewExited(VirtualContainerView.this, text.id);
+            }
+        }
+
+        public void setVisibilityIntegrationMode(VisibilityIntegrationMode mode) {
+            mVisibilityIntegrationMode = mode;
+        }
+
+        public void changeVisibility(boolean visible) {
+            if (mVisibilityIntegrationMode == null) {
+                throw new IllegalStateException("must call setVisibilityIntegrationMode() first");
+            }
+            if (this.visible == visible) {
+                return;
+            }
+            this.visible = visible;
+            Log.d(TAG, "visibility changed view: " + text.id + "; visible:" + visible
+                    + "; integrationMode: " + mVisibilityIntegrationMode);
+            if (mVisibilityIntegrationMode == VisibilityIntegrationMode.NOTIFY_AFM) {
+                mAfm.notifyViewVisibilityChanged(VirtualContainerView.this, text.id, visible);
+            }
+            invalidate();
+        }
+
+        public Rect getAbsCoordinates() {
+            // Must offset the boundaries so they're relative to the CustomView.
+            final int[] offset = new int[2];
+            getLocationOnScreen(offset);
+            final Rect absBounds = new Rect(bounds.left + offset[0],
+                    bounds.top + offset[1],
+                    bounds.right + offset[0], bounds.bottom + offset[1]);
+            Log.v(TAG, "getAbsCoordinates() for " + text.id + ": bounds=" + bounds
+                    + " offset: " + Arrays.toString(offset) + " absBounds: " + absBounds);
+            return absBounds;
+        }
+
+        public void setText(String value) {
+            text.text = value;
+            final AutofillManager autofillManager =
+                    getContext().getSystemService(AutofillManager.class);
+            if (mCompatMode) {
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, text.id);
+            } else {
+                if (autofillManager != null) {
+                    autofillManager.notifyValueChanged(VirtualContainerView.this, text.id,
+                            AutofillValue.forText(text.text));
+                }
+            }
+            invalidate();
+        }
+
+        public void setTextChangedListener(TextWatcher listener) {
+            text.listener = listener;
+        }
+
+        @Override
+        public String toString() {
+            return "Label: " + label + " Text: " + text + " Focused: " + focused
+                    + " Visible: " + visible;
+        }
+
+        final class OneTimeLineWatcher implements TextWatcher {
+            private final CountDownLatch latch;
+            private final CharSequence expected;
+
+            OneTimeLineWatcher(CharSequence expectedValue) {
+                this.expected = expectedValue;
+                this.latch = new CountDownLatch(1);
+            }
+
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                latch.countDown();
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+            }
+
+            void assertAutoFilled() throws Exception {
+                final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+                assertWithMessage("Timeout (%s ms) on Line %s", FILL_TIMEOUT.ms(), label)
+                        .that(set).isTrue();
+                final String actual = text.text.toString();
+                assertWithMessage("Wrong auto-fill value on Line %s", label)
+                        .that(actual).isEqualTo(expected.toString());
+            }
+        }
+    }
+
+    public static final class Item {
+        private final Line line;
+        public final int id;
+        private final String resourceId;
+        private CharSequence text;
+        private final boolean editable;
+        private final boolean sensitive;
+        private final String className;
+        private TextWatcher listener;
+
+        public Item(Line line, int id, String resourceId, CharSequence text, boolean editable,
+                boolean sensitive) {
+            this.line = line;
+            this.id = id;
+            this.resourceId = resourceId;
+            this.text = text;
+            this.editable = editable;
+            this.sensitive = sensitive;
+            this.className = editable ? TEXT_CLASS : LABEL_CLASS;
+        }
+
+        public AccessibilityNodeInfo provideAccessibilityNodeInfo(View parent, Context context) {
+            final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+            node.setSource(parent, id);
+            node.setPackageName(context.getPackageName());
+            node.setClassName(className);
+            node.setEditable(editable);
+            node.setViewIdResourceName(resourceId);
+            node.setVisibleToUser(true);
+            node.setInputType(line.inputType);
+            if (line.absBounds != null) {
+                node.setBoundsInScreen(line.absBounds);
+            }
+            if (TextUtils.getTrimmedLength(text) > 0) {
+                // TODO: Must checked trimmed length because input fields use 8 empty spaces to
+                // set width
+                node.setText(text);
+            }
+            return node;
+        }
+
+        private void autofill(CharSequence value) {
+            if (!editable) {
+                Log.w(TAG, "Item for id " + id + " is not editable: " + this);
+                return;
+            }
+            text = value;
+            if (listener != null) {
+                Log.d(TAG, "Notify listener: " + text);
+                listener.onTextChanged(text, 0, 0, 0);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return id + "/" + resourceId + ": " + text + (editable ? " (editable)" : " (read-only)"
+                    + (sensitive ? " (sensitive)" : " (sanitized"));
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewActivity.java
new file mode 100644
index 0000000..d795209
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewActivity.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Timeouts.WEBVIEW_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.View;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class WebViewActivity extends AbstractWebViewActivity {
+
+    private static final String TAG = "WebViewActivity";
+    private static final String FAKE_URL = "https://" + FAKE_DOMAIN + ":666/login.html";
+    static final String ID_WEBVIEW = "webview";
+
+    public static final String ID_OUTSIDE1 = "outside1";
+    public static final String ID_OUTSIDE2 = "outside2";
+
+    public EditText mOutside1;
+    public EditText mOutside2;
+
+    private LinearLayout mParent;
+    private LinearLayout mOutsideContainer1;
+    private LinearLayout mOutsideContainer2;
+
+    private UiObject2 mUsernameLabel;
+    private UiObject2 mUsernameInput;
+    private UiObject2 mPasswordLabel;
+    private UiObject2 mPasswordInput;
+    private UiObject2 mLoginButton;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.webview_activity);
+
+        mParent = findViewById(R.id.parent);
+        mOutsideContainer1 = findViewById(R.id.outsideContainer1);
+        mOutsideContainer2 = findViewById(R.id.outsideContainer2);
+        mOutside1 = findViewById(R.id.outside1);
+        mOutside2 = findViewById(R.id.outside2);
+    }
+
+    public MyWebView loadWebView(UiBot uiBot) throws Exception {
+        return loadWebView(uiBot, false);
+    }
+
+    public MyWebView loadWebView(UiBot uiBot, boolean usingAppContext) throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        syncRunOnUiThread(() -> {
+            final Context context = usingAppContext ? getApplicationContext() : this;
+            mWebView = new MyWebView(context);
+            mParent.addView(mWebView);
+            mWebView.setWebViewClient(new WebViewClient() {
+                // WebView does not set the WebDomain on file:// requests, so we need to use an
+                // https:// request and intercept it to provide the real data.
+                @Override
+                public WebResourceResponse shouldInterceptRequest(WebView view,
+                        WebResourceRequest request) {
+                    final String url = request.getUrl().toString();
+                    if (!url.equals(FAKE_URL)) {
+                        Log.d(TAG, "Ignoring " + url);
+                        return super.shouldInterceptRequest(view, request);
+                    }
+
+                    final String rawPath = request.getUrl().getPath()
+                            .substring(1); // Remove leading /
+                    Log.d(TAG, "Converting " + url + " to " + rawPath);
+                    // NOTE: cannot use try-with-resources because it would close the stream before
+                    // WebView uses it.
+                    try {
+                        return new WebResourceResponse("text/html", "utf-8",
+                                getAssets().open(rawPath));
+                    } catch (IOException e) {
+                        throw new IllegalArgumentException("Error opening " + rawPath, e);
+                    }
+                }
+
+                @Override
+                public void onPageFinished(WebView view, String url) {
+                    Log.v(TAG, "onPageFinished(): " + url);
+                    latch.countDown();
+                }
+            });
+            mWebView.loadUrl(FAKE_URL);
+        });
+
+        // Wait until it's loaded.
+        if (!latch.await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(WEBVIEW_TIMEOUT, "WebView not loaded");
+        }
+
+        // Validation check to make sure autofill was enabled when the WebView was created
+        assertThat(mWebView.isAutofillEnabled()).isTrue();
+
+        // WebView builds its accessibility tree asynchronously and only after being queried the
+        // first time, so we should first find the WebView and query some of its properties,
+        // wait for its accessibility tree to be populated (by blocking until a known element
+        // appears), then cache the objects for further use.
+
+        // NOTE: we cannot search by resourceId because WebView does not set them...
+
+        // Wait for known element...
+        mUsernameLabel = uiBot.assertShownByText("Username: ", WEBVIEW_TIMEOUT);
+        // ...then cache the others
+        mUsernameInput = getInput(uiBot, mUsernameLabel);
+        mPasswordLabel = uiBot.findRightAwayByText("Password: ");
+        mPasswordInput = getInput(uiBot, mPasswordLabel);
+        mLoginButton = uiBot.findRightAwayByText("Login");
+
+        return mWebView;
+    }
+
+    public void loadOutsideViews() {
+        syncRunOnUiThread(() -> {
+            mOutsideContainer1.setVisibility(View.VISIBLE);
+            mOutsideContainer2.setVisibility(View.VISIBLE);
+        });
+    }
+
+    public UiObject2 getUsernameLabel() throws Exception {
+        return mUsernameLabel;
+    }
+
+    public UiObject2 getPasswordLabel() throws Exception {
+        return mPasswordLabel;
+    }
+
+    public UiObject2 getUsernameInput() throws Exception {
+        return mUsernameInput;
+    }
+
+    public UiObject2 getPasswordInput() throws Exception {
+        return mPasswordInput;
+    }
+
+    public UiObject2 getLoginButton() throws Exception {
+        return mLoginButton;
+    }
+
+    @Override
+    public void clearFocus() {
+        syncRunOnUiThread(() -> mParent.requestFocus());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewMultiScreenLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewMultiScreenLoginActivity.java
new file mode 100644
index 0000000..56684ab
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewMultiScreenLoginActivity.java
@@ -0,0 +1,175 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Timeouts.WEBVIEW_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.UiBot;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class WebViewMultiScreenLoginActivity extends AbstractWebViewActivity {
+
+    private static final String TAG = "WebViewMultiScreenLoginActivity";
+    private static final String FAKE_USERNAME_URL = "https://" + FAKE_DOMAIN + ":666/username.html";
+    private static final String FAKE_PASSWORD_URL = "https://" + FAKE_DOMAIN + ":666/password.html";
+
+    private UiObject2 mUsernameLabel;
+    private UiObject2 mUsernameInput;
+    private UiObject2 mNextButton;
+
+    private UiObject2 mPasswordLabel;
+    private UiObject2 mPasswordInput;
+    private UiObject2 mLoginButton;
+
+    private final Map<String, CountDownLatch> mLatches = new HashMap<>();
+
+    public WebViewMultiScreenLoginActivity() {
+        mLatches.put(FAKE_USERNAME_URL, new CountDownLatch(1));
+        mLatches.put(FAKE_PASSWORD_URL, new CountDownLatch(1));
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.webview_only_activity);
+        mWebView = findViewById(R.id.my_webview);
+    }
+
+    public MyWebView loadWebView(UiBot uiBot) throws Exception {
+        syncRunOnUiThread(() -> {
+            mWebView.setWebViewClient(new WebViewClient() {
+                // WebView does not set the WebDomain on file:// requests, so we need to use an
+                // https:// request and intercept it to provide the real data.
+                @Override
+                public WebResourceResponse shouldInterceptRequest(WebView view,
+                        WebResourceRequest request) {
+                    final String url = request.getUrl().toString();
+                    if (!url.equals(FAKE_USERNAME_URL) && !url.equals(FAKE_PASSWORD_URL)) {
+                        Log.d(TAG, "Ignoring " + url);
+                        return super.shouldInterceptRequest(view, request);
+                    }
+
+                    final String rawPath = request.getUrl().getPath()
+                            .substring(1); // Remove leading /
+                    Log.d(TAG, "Converting " + url + " to " + rawPath);
+                    // NOTE: cannot use try-with-resources because it would close the stream before
+                    // WebView uses it.
+                    try {
+                        return new WebResourceResponse("text/html", "utf-8",
+                                getAssets().open(rawPath));
+                    } catch (IOException e) {
+                        throw new IllegalArgumentException("Error opening " + rawPath, e);
+                    }
+                }
+
+                @Override
+                public void onPageFinished(WebView view, String url) {
+                    final CountDownLatch latch = mLatches.get(url);
+                    Log.v(TAG, "onPageFinished(): " + url + " latch: " + latch);
+                    if (latch != null) {
+                        latch.countDown();
+                    }
+                }
+            });
+            mWebView.loadUrl(FAKE_USERNAME_URL);
+        });
+
+        // Wait until it's loaded.
+        if (!mLatches.get(FAKE_USERNAME_URL).await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(WEBVIEW_TIMEOUT, "WebView not loaded");
+        }
+
+        // Validation check to make sure autofill was enabled when the WebView was created
+        assertThat(mWebView.isAutofillEnabled()).isTrue();
+
+        // WebView builds its accessibility tree asynchronously and only after being queried the
+        // first time, so we should first find the WebView and query some of its properties,
+        // wait for its accessibility tree to be populated (by blocking until a known element
+        // appears), then cache the objects for further use.
+
+        // NOTE: we cannot search by resourceId because WebView does not set them...
+
+        // Wait for known element...
+        mUsernameLabel = uiBot.assertShownByText("Username: ", WEBVIEW_TIMEOUT);
+        // ...then cache the others
+        mUsernameInput = getInput(uiBot, mUsernameLabel);
+        mNextButton = uiBot.findRightAwayByText("Next");
+
+        return mWebView;
+    }
+
+    public void waitForPasswordScreen(UiBot uiBot) throws Exception {
+        // Wait until it's loaded.
+        if (!mLatches.get(FAKE_PASSWORD_URL).await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(WEBVIEW_TIMEOUT, "Password page not loaded");
+        }
+
+        // WebView builds its accessibility tree asynchronously and only after being queried the
+        // first time, so we should first find the WebView and query some of its properties,
+        // wait for its accessibility tree to be populated (by blocking until a known element
+        // appears), then cache the objects for further use.
+
+        // NOTE: we cannot search by resourceId because WebView does not set them...
+
+        // Wait for known element...
+        mPasswordLabel = uiBot.assertShownByText("Password: ", WEBVIEW_TIMEOUT);
+        // ...then cache the others
+        mPasswordInput = getInput(uiBot, mPasswordLabel);
+        mLoginButton = uiBot.findRightAwayByText("Login");
+    }
+
+    public UiObject2 getUsernameLabel() throws Exception {
+        return mUsernameLabel;
+    }
+
+    public UiObject2 getUsernameInput() throws Exception {
+        return mUsernameInput;
+    }
+
+    public UiObject2 getNextButton() throws Exception {
+        return mNextButton;
+    }
+
+    public UiObject2 getPasswordLabel() throws Exception {
+        return mPasswordLabel;
+    }
+
+    public UiObject2 getPasswordInput() throws Exception {
+        return mPasswordInput;
+    }
+
+    public UiObject2 getLoginButton() throws Exception {
+        return mLoginButton;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/WelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/WelcomeActivity.java
new file mode 100644
index 0000000..786d56f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/WelcomeActivity.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.PendingIntent;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Activity that displays a "Welcome USER" message after login.
+ */
+public class WelcomeActivity extends AbstractAutoFillActivity {
+
+    private static WelcomeActivity sInstance;
+
+    private static final String TAG = "WelcomeActivity";
+
+    public static final String EXTRA_MESSAGE = "message";
+    public static final String ID_WELCOME = "welcome";
+
+    private static int sPendingIntentId;
+    private static PendingIntent sPendingIntent;
+
+    private TextView mWelcome;
+
+    public WelcomeActivity() {
+        sInstance = this;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.welcome_activity);
+
+        mWelcome = (TextView) findViewById(R.id.welcome);
+
+        final Intent intent = getIntent();
+        final String message = intent.getStringExtra(EXTRA_MESSAGE);
+
+        if (!TextUtils.isEmpty(message)) {
+            mWelcome.setText(message);
+        }
+
+        Log.d(TAG, "Message: " + message);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Setting sInstance to null onDestroy()");
+        sInstance = null;
+    }
+
+    @Override
+    public void finish() {
+        super.finish();
+        Log.d(TAG, "So long and thanks for all the finish!");
+
+        if (sPendingIntent != null) {
+            Log.v(TAG, " canceling pending intent on finish(): " + sPendingIntent);
+            sPendingIntent.cancel();
+        }
+    }
+
+    public static void finishIt() {
+        if (sInstance != null) {
+            sInstance.finish();
+        }
+    }
+
+    // TODO: reuse in other places
+    public static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
+        assertShowing(uiBot, null);
+    }
+
+    // TODO: reuse in other places
+    public static void assertShowing(UiBot uiBot, @Nullable String expectedMessage)
+            throws Exception {
+        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
+        if (expectedMessage == null) {
+            expectedMessage = "Welcome to the jungle!";
+        }
+        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
+                .isEqualTo(expectedMessage);
+    }
+
+    public static IntentSender createSender(Context context, String message) {
+        if (sPendingIntent != null) {
+            throw new IllegalArgumentException("Already have pending intent (id="
+                    + sPendingIntentId + "): " + sPendingIntent);
+        }
+        ++sPendingIntentId;
+        Log.v(TAG, "createSender: id=" + sPendingIntentId + " message=" + message);
+        final Intent intent = new Intent(context, WelcomeActivity.class)
+                .putExtra(EXTRA_MESSAGE, message)
+                .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+        sPendingIntent = PendingIntent.getActivity(context, sPendingIntentId, intent, 0);
+        return sPendingIntent.getIntentSender();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AbstractLoginNotImportantForAutofillTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AbstractLoginNotImportantForAutofillTestCase.java
deleted file mode 100644
index 67169b4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AbstractLoginNotImportantForAutofillTestCase.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
-
-import android.autofillservice.cts.LoginNotImportantForAutofillActivity;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.EditText;
-
-import org.junit.Test;
-
-abstract class AbstractLoginNotImportantForAutofillTestCase<A extends
-        LoginNotImportantForAutofillActivity> extends
-        AugmentedAutofillAutoActivityLaunchTestCase<A> {
-
-    protected A mActivity;
-
-    @Test
-    public void testAutofill_none() throws Exception {
-        // Set services
-        enableService();
-        enableAugmentedService();
-
-        // Set expectations
-        final EditText username = mActivity.getUsername();
-        final AutofillValue expectedFocusedValue = username.getAutofillValue();
-        final AutofillId expectedFocusedId = username.getAutofillId();
-        sAugmentedReplier.addResponse(NO_AUGMENTED_RESPONSE);
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        assertBasicRequestInfo(request, mActivity, expectedFocusedId, expectedFocusedValue);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is not shown.
-        mAugmentedUiBot.assertUiNeverShown();
-    }
-
-    @Test
-    public void testAutofill_oneField() throws Exception {
-        // Set services
-        enableService();
-        enableAugmentedService();
-
-        // Set expectations
-        final EditText username = mActivity.getUsername();
-        final AutofillId usernameId = username.getAutofillId();
-        final AutofillValue expectedFocusedValue = username.getAutofillValue();
-        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
-                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
-                        .setField(usernameId, "dude")
-                        .build(), usernameId)
-                .build());
-        mActivity.expectAutoFill("dude");
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is shown.
-        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
-
-        // Autofill
-        ui.click();
-        mActivity.assertAutoFilled();
-        mAugmentedUiBot.assertUiGone();
-    }
-
-    @Test
-    public void testAutofill_twoFields() throws Exception {
-        // Set services
-        enableService();
-        enableAugmentedService();
-
-        // Set expectations
-        final EditText username = mActivity.getUsername();
-        final AutofillId usernameId = username.getAutofillId();
-        final AutofillValue expectedFocusedValue = username.getAutofillValue();
-        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
-                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
-                        .setField(usernameId, "dude")
-                        .setField(mActivity.getPassword().getAutofillId(), "sweet")
-                        .build(), usernameId)
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is shown.
-        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
-
-        // Autofill
-        ui.click();
-        mActivity.assertAutoFilled();
-        mAugmentedUiBot.assertUiGone();
-    }
-
-    @Test
-    public void testAutofill_manualRequest() throws Exception {
-        // Set services
-        enableService();
-        enableAugmentedService();
-
-        // Set expectations
-        final EditText username = mActivity.getUsername();
-        final AutofillId usernameId = username.getAutofillId();
-        final AutofillValue expectedFocusedValue = username.getAutofillValue();
-        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
-                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
-                        .setField(usernameId, "dude")
-                        .build(), usernameId)
-                .build());
-        mActivity.expectAutoFill("dude");
-
-        // Trigger autofill
-        mActivity.forceAutofillOnUsername();
-        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        // No inline request because didn't focus on any view.
-        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue,
-                /* hasInlineRequest */ false);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is shown.
-        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
-
-        // Autofill
-        ui.click();
-        mActivity.assertAutoFilled();
-        mAugmentedUiBot.assertUiGone();
-    }
-
-    @Test
-    public void testAutofill_autoThenManualRequests() throws Exception {
-        // Set services
-        enableService();
-        enableAugmentedService();
-
-        // Set expectations
-        final EditText username = mActivity.getUsername();
-        final AutofillId usernameId = username.getAutofillId();
-        final AutofillValue expectedFocusedValue = username.getAutofillValue();
-        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
-                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
-                        .setField(usernameId, "WHATEVER")
-                        .build(), usernameId)
-                .build());
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        final AugmentedFillRequest request1 = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        assertBasicRequestInfo(request1, mActivity, usernameId, expectedFocusedValue);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is shown.
-        mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
-
-        sReplier.addResponse(NO_RESPONSE);
-        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
-                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Fill Me")
-                        .setField(usernameId, "dude")
-                        .build(), usernameId)
-                .build());
-        mActivity.expectAutoFill("dude");
-
-        // Trigger autofill
-        mActivity.clearFocus();
-        mActivity.forceAutofillOnUsername();
-        sReplier.getNextFillRequest();
-        final AugmentedFillRequest request2 = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        assertBasicRequestInfo(request2, mActivity, usernameId, expectedFocusedValue);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is shown.
-        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Fill Me");
-
-        // Autofill
-        ui.click();
-        mActivity.assertAutoFilled();
-        mAugmentedUiBot.assertUiGone();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAuthActivity.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAuthActivity.java
deleted file mode 100644
index 8de9eb7..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAuthActivity.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts.augmented;
-
-import android.app.PendingIntent;
-import android.autofillservice.cts.AbstractAutoFillActivity;
-import android.autofillservice.cts.R;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.service.autofill.Dataset;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Activity for testing Augmented Autofill authentication flow. This activity shows a simple UI;
- * when the UI is tapped, it returns whatever data was configured via the auth intent.
- */
-public class AugmentedAuthActivity extends AbstractAutoFillActivity {
-    private static final String TAG = "AugmentedAuthActivity";
-
-    public static final String ID_AUTH_ACTIVITY_BUTTON = "button";
-
-    private static final String EXTRA_DATASET_TO_RETURN = "dataset_to_return";
-    private static final String EXTRA_CLIENT_STATE_TO_RETURN = "client_state_to_return";
-    private static final String EXTRA_RESULT_CODE_TO_RETURN = "result_code_to_return";
-
-    private static final List<PendingIntent> sPendingIntents = new ArrayList<>(1);
-
-    public static void resetStaticState() {
-        for (PendingIntent pendingIntent : sPendingIntents) {
-            pendingIntent.cancel();
-        }
-        sPendingIntents.clear();
-    }
-
-    public static IntentSender createSender(Context context, int requestCode,
-            Dataset datasetToReturn, Bundle clientStateToReturn, int resultCodeToReturn) {
-        Intent intent = new Intent(context, AugmentedAuthActivity.class);
-        intent.putExtra(EXTRA_DATASET_TO_RETURN, datasetToReturn);
-        intent.putExtra(EXTRA_CLIENT_STATE_TO_RETURN, clientStateToReturn);
-        intent.putExtra(EXTRA_RESULT_CODE_TO_RETURN, resultCodeToReturn);
-        PendingIntent pendingIntent = PendingIntent.getActivity(context, requestCode, intent, 0);
-        sPendingIntents.add(pendingIntent);
-        return pendingIntent.getIntentSender();
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Log.d(TAG, "Auth activity invoked, showing auth UI");
-        setContentView(R.layout.single_button_activity);
-        findViewById(R.id.button).setOnClickListener((v) -> {
-            Log.d(TAG, "Auth UI tapped, returning result");
-
-            Intent intent = getIntent();
-            Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET_TO_RETURN);
-            Bundle clientState = intent.getParcelableExtra(EXTRA_CLIENT_STATE_TO_RETURN);
-            int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE_TO_RETURN, RESULT_OK);
-            Log.d(TAG, "Output: dataset=" + dataset + ", clientState=" + clientState
-                    + ", resultCode=" + resultCode);
-
-            Intent result = new Intent();
-            result.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, dataset);
-            result.putExtra(AutofillManager.EXTRA_CLIENT_STATE, clientState);
-            setResult(resultCode, result);
-
-            finish();
-        });
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java
deleted file mode 100644
index d0d61ec..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.Helper.allowOverlays;
-import static android.autofillservice.cts.Helper.disallowOverlays;
-
-import android.autofillservice.cts.AbstractAutoFillActivity;
-import android.autofillservice.cts.AutoFillServiceTestCase;
-import android.autofillservice.cts.UiBot;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedReplier;
-import android.content.AutofillOptions;
-import android.view.autofill.AutofillManager;
-
-import com.android.compatibility.common.util.RequiredSystemResourceRule;
-
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-
-/////
-///// NOTE: changes in this class should also be applied to
-/////       AugmentedAutofillManualActivityLaunchTestCase, which is exactly the same as this except
-/////       by which class it extends.
-
-// Must be public because of the @ClassRule
-public abstract class AugmentedAutofillAutoActivityLaunchTestCase
-        <A extends AbstractAutoFillActivity> extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
-
-    protected static AugmentedReplier sAugmentedReplier;
-    protected AugmentedUiBot mAugmentedUiBot;
-
-    private CtsAugmentedAutofillService.ServiceWatcher mServiceWatcher;
-
-    private static final RequiredSystemResourceRule sRequiredResource =
-            new RequiredSystemResourceRule("config_defaultAugmentedAutofillService");
-
-    private static final RuleChain sRequiredFeatures = RuleChain
-            .outerRule(sRequiredFeatureRule)
-            .around(sRequiredResource);
-
-    public AugmentedAutofillAutoActivityLaunchTestCase() {}
-
-    public AugmentedAutofillAutoActivityLaunchTestCase(UiBot uiBot) {
-        super(uiBot);
-    }
-
-    @BeforeClass
-    public static void allowAugmentedAutofill() {
-        sContext.getApplicationContext()
-                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
-        allowOverlays();
-    }
-
-    @AfterClass
-    public static void resetAllowAugmentedAutofill() {
-        sContext.getApplicationContext().setAutofillOptions(null);
-        disallowOverlays();
-    }
-
-    @Before
-    public void setFixtures() {
-        mServiceWatcher = null;
-        sAugmentedReplier = CtsAugmentedAutofillService.getAugmentedReplier();
-        sAugmentedReplier.reset();
-        CtsAugmentedAutofillService.resetStaticState();
-        mAugmentedUiBot = new AugmentedUiBot(mUiBot);
-        mSafeCleanerRule
-                .run(() -> sAugmentedReplier.assertNoUnhandledFillRequests())
-                .run(() -> {
-                    AugmentedHelper.resetAugmentedService();
-                    if (mServiceWatcher != null) {
-                        mServiceWatcher.waitOnDisconnected();
-                    }
-                })
-                .add(() -> { return sAugmentedReplier.getExceptions(); });
-    }
-
-    @Override
-    protected int getNumberRetries() {
-        return 0; // A.K.A. "Optimistic Thinking"
-    }
-
-    @Override
-    protected int getSmartSuggestionMode() {
-        return AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
-    }
-
-    @Override
-    protected TestRule getRequiredFeaturesRule() {
-        return sRequiredFeatures;
-    }
-
-    protected CtsAugmentedAutofillService enableAugmentedService() throws InterruptedException {
-        if (mServiceWatcher != null) {
-            throw new IllegalStateException("There Can Be Only One!");
-        }
-
-        mServiceWatcher = CtsAugmentedAutofillService.setServiceWatcher();
-        AugmentedHelper.setAugmentedService(CtsAugmentedAutofillService.SERVICE_NAME);
-
-        CtsAugmentedAutofillService service = mServiceWatcher.waitOnConnected();
-        service.waitUntilConnected();
-        return service;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillManualActivityLaunchTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillManualActivityLaunchTestCase.java
deleted file mode 100644
index 32e6b88..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillManualActivityLaunchTestCase.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.Helper.allowOverlays;
-import static android.autofillservice.cts.Helper.disallowOverlays;
-
-import android.autofillservice.cts.AutoFillServiceTestCase;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedReplier;
-import android.content.AutofillOptions;
-import android.view.autofill.AutofillManager;
-
-import com.android.compatibility.common.util.RequiredSystemResourceRule;
-
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-
-/////
-///// NOTE: changes in this class should also be applied to
-/////       AugmentedAutofillManualActivityLaunchTestCase, which is exactly the same as this except
-/////       by which class it extends.
-
-// Must be public because of the @ClassRule
-public abstract class AugmentedAutofillManualActivityLaunchTestCase
-        extends AutoFillServiceTestCase.ManualActivityLaunch {
-
-    protected static AugmentedReplier sAugmentedReplier;
-    protected AugmentedUiBot mAugmentedUiBot;
-
-    private CtsAugmentedAutofillService.ServiceWatcher mServiceWatcher;
-
-    private static final RequiredSystemResourceRule sRequiredResource =
-            new RequiredSystemResourceRule("config_defaultAugmentedAutofillService");
-
-    private static final RuleChain sRequiredFeatures = RuleChain
-            .outerRule(sRequiredFeatureRule)
-            .around(sRequiredResource);
-
-    @BeforeClass
-    public static void allowAugmentedAutofill() {
-        sContext.getApplicationContext()
-                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
-        allowOverlays();
-    }
-
-    @AfterClass
-    public static void resetAllowAugmentedAutofill() {
-        sContext.getApplicationContext().setAutofillOptions(null);
-        disallowOverlays();
-    }
-
-    @Before
-    public void setFixtures() {
-        sAugmentedReplier = CtsAugmentedAutofillService.getAugmentedReplier();
-        sAugmentedReplier.reset();
-        CtsAugmentedAutofillService.resetStaticState();
-        mAugmentedUiBot = new AugmentedUiBot(mUiBot);
-        mSafeCleanerRule
-                .run(() -> sAugmentedReplier.assertNoUnhandledFillRequests())
-                .run(() -> {
-                    AugmentedHelper.resetAugmentedService();
-                    if (mServiceWatcher != null) {
-                        mServiceWatcher.waitOnDisconnected();
-                    }
-                })
-                .add(() -> {
-                    return sAugmentedReplier.getExceptions();
-                });
-    }
-
-    @Override
-    protected int getSmartSuggestionMode() {
-        return AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
-    }
-
-    @Override
-    protected TestRule getRequiredFeaturesRule() {
-        return sRequiredFeatures;
-    }
-
-    protected CtsAugmentedAutofillService enableAugmentedService() throws InterruptedException {
-        return enableAugmentedService(/* whitelistSelf= */ true);
-    }
-
-    protected CtsAugmentedAutofillService enableAugmentedService(boolean whitelistSelf)
-            throws InterruptedException {
-        if (mServiceWatcher != null) {
-            throw new IllegalStateException("There Can Be Only One!");
-        }
-
-        mServiceWatcher = CtsAugmentedAutofillService.setServiceWatcher();
-        AugmentedHelper.setAugmentedService(CtsAugmentedAutofillService.SERVICE_NAME);
-
-        CtsAugmentedAutofillService service = mServiceWatcher.waitOnConnected();
-        service.waitUntilConnected();
-        return service;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedHelper.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedHelper.java
deleted file mode 100644
index 56bf8b9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedHelper.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
-import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.Activity;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
-import android.content.ComponentName;
-import android.service.autofill.augmented.FillRequest;
-import android.util.Log;
-import android.util.Pair;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper for common funcionalities.
- */
-public final class AugmentedHelper {
-
-    private static final String TAG = AugmentedHelper.class.getSimpleName();
-
-    @NonNull
-    public static String getActivityName(@Nullable FillRequest request) {
-        if (request == null) return "N/A (null request)";
-
-        final ComponentName componentName = request.getActivityComponent();
-        if (componentName == null) return "N/A (no component name)";
-
-        return componentName.flattenToShortString();
-    }
-
-    /**
-     * Sets the augmented capture service.
-     */
-    public static void setAugmentedService(@NonNull String service) {
-        Log.d(TAG, "Setting service to " + service);
-        runShellCommand("cmd autofill set temporary-augmented-service 0 %s %d", service,
-                MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS);
-    }
-
-    /**
-     * Resets the content capture service.
-     */
-    public static void resetAugmentedService() {
-        Log.d(TAG, "Resetting back to default service");
-        runShellCommand("cmd autofill set temporary-augmented-service 0");
-    }
-
-    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
-            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull AutofillValue expectedFocusedValue) {
-        assertBasicRequestInfo(request, activity, expectedFocusedId,
-                expectedFocusedValue.getTextValue().toString());
-    }
-
-    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
-            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull String expectedFocusedValue) {
-        assertBasicRequestInfo(request, activity, expectedFocusedId, expectedFocusedValue, true);
-    }
-
-    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
-            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull AutofillValue expectedFocusedValue, boolean hasInlineRequest) {
-        assertBasicRequestInfo(request, activity, expectedFocusedId,
-                expectedFocusedValue.getTextValue().toString(), hasInlineRequest);
-    }
-
-    private static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
-            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull String expectedFocusedValue, boolean hasInlineRequest) {
-        Objects.requireNonNull(activity);
-        Objects.requireNonNull(expectedFocusedId);
-        assertWithMessage("no AugmentedFillRequest").that(request).isNotNull();
-        assertWithMessage("no FillRequest on %s", request).that(request.request).isNotNull();
-        assertWithMessage("no FillController on %s", request).that(request.controller).isNotNull();
-        assertWithMessage("no FillCallback on %s", request).that(request.callback).isNotNull();
-        assertWithMessage("no CancellationSignal on %s", request).that(request.cancellationSignal)
-                .isNotNull();
-        // NOTE: task id can change, we might need to set it in the activity's onCreate()
-        assertWithMessage("wrong task id on %s", request).that(request.request.getTaskId())
-                .isEqualTo(activity.getTaskId());
-
-        final ComponentName actualComponentName = request.request.getActivityComponent();
-        assertWithMessage("no activity name on %s", request).that(actualComponentName).isNotNull();
-        assertWithMessage("wrong activity name on %s", request).that(actualComponentName)
-                .isEqualTo(activity.getComponentName());
-        final AutofillId actualFocusedId = request.request.getFocusedId();
-        assertWithMessage("no focused id on %s", request).that(actualFocusedId).isNotNull();
-        assertWithMessage("wrong focused id on %s", request).that(actualFocusedId)
-                .isEqualTo(expectedFocusedId);
-        final AutofillValue actualFocusedValue = request.request.getFocusedValue();
-        assertWithMessage("no focused value on %s", request).that(actualFocusedValue).isNotNull();
-        assertAutofillValue(expectedFocusedValue, actualFocusedValue);
-        final InlineSuggestionsRequest inlineRequest =
-                request.request.getInlineSuggestionsRequest();
-        if (hasInlineRequest) {
-            assertWithMessage("no inline request on %s", request).that(inlineRequest).isNotNull();
-        } else {
-            assertWithMessage("exist inline request on %s", request).that(inlineRequest).isNull();
-        }
-    }
-
-    public static void assertAutofillValue(@NonNull AutofillValue expectedValue,
-            @NonNull AutofillValue actualValue) {
-        // It only supports text values for now...
-        assertWithMessage("expected value is not text: %s", expectedValue)
-                .that(expectedValue.isText()).isTrue();
-        assertAutofillValue(expectedValue.getTextValue().toString(), actualValue);
-    }
-
-    public static void assertAutofillValue(@NonNull String expectedValue,
-            @NonNull AutofillValue actualValue) {
-        assertWithMessage("actual value is not text: %s", actualValue)
-                .that(actualValue.isText()).isTrue();
-
-        assertWithMessage("wrong autofill value").that(actualValue.getTextValue().toString())
-                .isEqualTo(expectedValue);
-    }
-
-    @NonNull
-    public static String toString(@Nullable List<Pair<AutofillId, AutofillValue>> values) {
-        if (values == null) return "null";
-        final StringBuilder string = new StringBuilder("[");
-        final int size = values.size();
-        for (int i = 0; i < size; i++) {
-            final Pair<AutofillId, AutofillValue> value = values.get(i);
-            string.append(i).append(':').append(value.first).append('=')
-                   .append(Helper.toString(value.second));
-            if (i < size - 1) {
-                string.append(", ");
-            }
-
-        }
-        return string.append(']').toString();
-    }
-
-    @NonNull
-    public static String toString(@Nullable FillRequest request) {
-        if (request == null) return "(null request)";
-
-        final StringBuilder string =
-                new StringBuilder("FillRequest[act=").append(getActivityName(request))
-                .append(", taskId=").append(request.getTaskId());
-
-        final AutofillId focusedId = request.getFocusedId();
-        if (focusedId != null) {
-            string.append(", focusedId=").append(focusedId);
-        }
-        final AutofillValue focusedValue = request.getFocusedValue();
-        if (focusedValue != null) {
-            string.append(", focusedValue=").append(focusedValue);
-        }
-
-        return string.append(']').toString();
-    }
-
-    // Used internally by UiBot to assert the UI
-    static String getContentDescriptionForUi(@NonNull AutofillId focusedId) {
-        return "ui_for_" + focusedId;
-    }
-
-    private AugmentedHelper() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-
-    /**
-     * Awaits for a latch to be counted down.
-     */
-    public static void await(@NonNull CountDownLatch latch, @NonNull String fmt,
-            @Nullable Object... args)
-            throws InterruptedException {
-        final boolean called = latch.await(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        if (!called) {
-            throw new IllegalStateException(String.format(fmt, args)
-                    + " in " + CONNECTION_TIMEOUT.ms() + "ms");
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivity.java
deleted file mode 100644
index 8d7c412..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts.augmented;
-
-import android.autofillservice.cts.LoginActivity;
-
-// Currently it's same as LoginActivity, except that it allows rotation.
-public class AugmentedLoginActivity extends LoginActivity {
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
index 2683ec4..47dfdfb2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
@@ -16,20 +16,20 @@
 
 package android.autofillservice.cts.augmented;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertHasFlags;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertViewAutofillState;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.autofillservice.cts.UiBot.LANDSCAPE;
-import static android.autofillservice.cts.UiBot.PORTRAIT;
-import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
-import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.DO_NOT_REPLY_AUGMENTED_RESPONSE;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.DO_NOT_REPLY_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertViewAutofillState;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.UiBot.LANDSCAPE;
+import static android.autofillservice.cts.testcore.UiBot.PORTRAIT;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 
@@ -40,16 +40,21 @@
 import static org.testng.Assert.assertThrows;
 
 import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.autofillservice.cts.LoginActivity;
-import android.autofillservice.cts.MyAutofillCallback;
-import android.autofillservice.cts.OneTimeCancellationSignalListener;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.autofillservice.cts.activities.AugmentedLoginActivity;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.commontests.AugmentedAutofillAutoActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.AugmentedHelper;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.OneTimeCancellationSignalListener;
 import android.content.ComponentName;
 import android.os.CancellationSignal;
 import android.platform.test.annotations.AppModeFull;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginNotImportantForAutofillActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginNotImportantForAutofillActivityTest.java
index 3875561..2540488 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginNotImportantForAutofillActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginNotImportantForAutofillActivityTest.java
@@ -16,8 +16,9 @@
 
 package android.autofillservice.cts.augmented;
 
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.LoginNotImportantForAutofillActivity;
+import android.autofillservice.cts.activities.LoginNotImportantForAutofillActivity;
+import android.autofillservice.cts.commontests.AbstractLoginNotImportantForAutofillTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
 import android.platform.test.annotations.AppModeFull;
 
 @AppModeFull(reason = "AugmentedLoginActivityTest is enough")
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedActivityContextTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedActivityContextTest.java
index e4300ac..c28ec14 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedActivityContextTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedActivityContextTest.java
@@ -16,8 +16,9 @@
 
 package android.autofillservice.cts.augmented;
 
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.LoginNotImportantForAutofillWrappedActivityContextActivity;
+import android.autofillservice.cts.activities.LoginNotImportantForAutofillWrappedActivityContextActivity;
+import android.autofillservice.cts.commontests.AbstractLoginNotImportantForAutofillTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
 import android.platform.test.annotations.AppModeFull;
 
 @AppModeFull(reason = "AugmentedLoginActivityTest is enough")
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedApplicationContextTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedApplicationContextTest.java
index efdb036..88b6486 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedApplicationContextTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedApplicationContextTest.java
@@ -16,8 +16,9 @@
 
 package android.autofillservice.cts.augmented;
 
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.LoginNotImportantForAutofillWrappedApplicationContextActivity;
+import android.autofillservice.cts.activities.LoginNotImportantForAutofillWrappedApplicationContextActivity;
+import android.autofillservice.cts.commontests.AbstractLoginNotImportantForAutofillTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
 import android.platform.test.annotations.AppModeFull;
 
 @AppModeFull(reason = "AugmentedLoginActivityTest is enough")
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedTimeouts.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedTimeouts.java
deleted file mode 100644
index 7bfdfc8..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedTimeouts.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts.augmented;
-
-import com.android.compatibility.common.util.Timeout;
-
-/**
- * Timeouts for common tasks.
- */
-final class AugmentedTimeouts {
-
-    private static final long ONE_TIMEOUT_TO_RULE_THEN_ALL_MS = 1_000;
-    private static final long ONE_NAPTIME_TO_RULE_THEN_ALL_MS = 3_000;
-
-    /**
-     * Timeout for expected augmented autofill requests.
-     */
-    static final Timeout AUGMENTED_FILL_TIMEOUT = new Timeout("AUGMENTED_FILL_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout until framework binds / unbinds from service.
-     */
-    static final Timeout AUGMENTED_CONNECTION_TIMEOUT = new Timeout("AUGMENTED_CONNECTION_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout used when the augmented autofill UI not expected to be shown - test will sleep for
-     * that amount of time as there is no callback that be received to assert it's not shown.
-     */
-    static final long AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
-
-    private AugmentedTimeouts() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedUiBot.java
deleted file mode 100644
index 5f9bca5..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedUiBot.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.augmented.AugmentedHelper.getContentDescriptionForUi;
-import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
-import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.R;
-import android.autofillservice.cts.UiBot;
-import android.support.test.uiautomator.UiObject2;
-import android.view.autofill.AutofillId;
-
-import androidx.annotation.NonNull;
-
-import com.google.common.base.Preconditions;
-
-import java.util.Objects;
-
-/**
- * Helper for UI-related needs.
- */
-public final class AugmentedUiBot {
-
-    private final UiBot mUiBot;
-    private boolean mOkToCallAssertUiGone;
-
-    public AugmentedUiBot(@NonNull UiBot uiBot) {
-        mUiBot = uiBot;
-    }
-
-    /**
-     * Asserts the augmented autofill UI was never shown.
-     *
-     * <p>This method is slower than {@link #assertUiGone()} and should only be called in the
-     * cases where the dataset picker was not previous shown.
-     */
-    public void assertUiNeverShown() throws Exception {
-        mUiBot.assertNeverShownByRelativeId("augmented autofil UI", R.id.augmentedAutofillUi,
-                AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS);
-    }
-
-    /**
-     * Asserts the augmented autofill UI was shown.
-     *
-     * @param focusedId where it should have been shown
-     * @param expectedText the expected text in the UI
-     */
-    public UiObject2 assertUiShown(@NonNull AutofillId focusedId,
-            @NonNull String expectedText) throws Exception {
-        Objects.requireNonNull(focusedId);
-        Objects.requireNonNull(expectedText);
-
-        final UiObject2 ui = mUiBot.assertShownByRelativeId(R.id.augmentedAutofillUi);
-
-        assertWithMessage("Wrong text on UI").that(ui.getText()).isEqualTo(expectedText);
-
-        final String expectedContentDescription = getContentDescriptionForUi(focusedId);
-        assertWithMessage("Wrong content description on UI")
-                .that(ui.getContentDescription()).isEqualTo(expectedContentDescription);
-
-        mOkToCallAssertUiGone = true;
-
-        return ui;
-    }
-
-    /**
-     * Asserts the augmented autofill UI is gone AFTER it was previously shown.
-     *
-     * @throws IllegalStateException if this method is called without calling
-     * {@link #assertUiShown(AutofillId, String)} before.
-     */
-    public void assertUiGone() {
-        Preconditions.checkState(mOkToCallAssertUiGone, "must call assertUiShown() first");
-        mUiBot.assertGoneByRelativeId(R.id.augmentedAutofillUi, AUGMENTED_FILL_TIMEOUT);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java
deleted file mode 100644
index af1229b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.augmented.AugmentedHelper.getContentDescriptionForUi;
-
-import android.autofillservice.cts.R;
-import android.content.Context;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.service.autofill.InlinePresentation;
-import android.service.autofill.augmented.FillCallback;
-import android.service.autofill.augmented.FillController;
-import android.service.autofill.augmented.FillRequest;
-import android.service.autofill.augmented.FillResponse;
-import android.service.autofill.augmented.FillWindow;
-import android.service.autofill.augmented.PresentationParams;
-import android.service.autofill.augmented.PresentationParams.Area;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-/**
- * Helper class used to produce a {@link FillResponse}.
- */
-public final class CannedAugmentedFillResponse {
-
-    private static final String TAG = CannedAugmentedFillResponse.class.getSimpleName();
-
-    public static final String CLIENT_STATE_KEY = "clientStateKey";
-    public static final String CLIENT_STATE_VALUE = "clientStateValue";
-
-    private final AugmentedResponseType mResponseType;
-    private final Map<AutofillId, Dataset> mDatasets;
-    private long mDelay;
-    private final Dataset mOnlyDataset;
-    private final @Nullable List<Dataset> mInlineSuggestions;
-
-    private CannedAugmentedFillResponse(@NonNull Builder builder) {
-        mResponseType = builder.mResponseType;
-        mDatasets = builder.mDatasets;
-        mDelay = builder.mDelay;
-        mOnlyDataset = builder.mOnlyDataset;
-        mInlineSuggestions = builder.mInlineSuggestions;
-    }
-
-    /**
-     * Constant used to pass a {@code null} response to the
-     * {@link FillCallback#onSuccess(FillResponse)} method.
-     */
-    public static final CannedAugmentedFillResponse NO_AUGMENTED_RESPONSE =
-            new Builder(AugmentedResponseType.NULL).build();
-
-    /**
-     * Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
-     */
-    public static final CannedAugmentedFillResponse DO_NOT_REPLY_AUGMENTED_RESPONSE =
-            new Builder(AugmentedResponseType.TIMEOUT).build();
-
-    public AugmentedResponseType getResponseType() {
-        return mResponseType;
-    }
-
-    public long getDelay() {
-        return mDelay;
-    }
-
-    /**
-     * Creates the "real" response.
-     */
-    public FillResponse asFillResponse(@NonNull Context context, @NonNull FillRequest request,
-            @NonNull FillController controller) {
-        final AutofillId focusedId = request.getFocusedId();
-
-        final Dataset dataset;
-        if (mOnlyDataset != null) {
-            dataset = mOnlyDataset;
-        } else {
-            dataset = mDatasets.get(focusedId);
-        }
-        if (dataset == null) {
-            Log.d(TAG, "no dataset for field " + focusedId);
-            return null;
-        }
-
-        Log.d(TAG, "asFillResponse: id=" + focusedId + ", dataset=" + dataset);
-
-        final PresentationParams presentationParams = request.getPresentationParams();
-        if (presentationParams == null) {
-            Log.w(TAG, "No PresentationParams");
-            return null;
-        }
-
-        final Area strip = presentationParams.getSuggestionArea();
-        if (strip == null) {
-            Log.w(TAG, "No suggestion strip");
-            return null;
-        }
-
-        if (mInlineSuggestions != null) {
-            return createResponseWithInlineSuggestion();
-        }
-
-        final LayoutInflater inflater = LayoutInflater.from(context);
-        final TextView rootView = (TextView) inflater.inflate(R.layout.augmented_autofill_ui, null);
-
-        Log.d(TAG, "Setting autofill UI text to:" + dataset.mPresentation);
-        rootView.setText(dataset.mPresentation);
-
-        rootView.setContentDescription(getContentDescriptionForUi(focusedId));
-        final FillWindow fillWindow = new FillWindow();
-        rootView.setOnClickListener((v) -> {
-            Log.d(TAG, "Destroying window first");
-            fillWindow.destroy();
-            final List<Pair<AutofillId, AutofillValue>> values;
-            final AutofillValue onlyValue = dataset.getOnlyFieldValue();
-            if (onlyValue != null) {
-                Log.i(TAG, "Autofilling only value for " + focusedId + " as " + onlyValue);
-                values = new ArrayList<>(1);
-                values.add(new Pair<AutofillId, AutofillValue>(focusedId, onlyValue));
-            } else {
-                values = dataset.getValues();
-                Log.i(TAG, "Autofilling: " + AugmentedHelper.toString(values));
-            }
-            controller.autofill(values);
-        });
-
-        boolean ok = fillWindow.update(strip, rootView, 0);
-        if (!ok) {
-            Log.w(TAG, "FillWindow.update() failed for " + strip + " and " + rootView);
-            return null;
-        }
-
-        return new FillResponse.Builder().setFillWindow(fillWindow).build();
-    }
-
-    @Override
-    public String toString() {
-        return "CannedAugmentedFillResponse: [type=" + mResponseType
-                + ", onlyDataset=" + mOnlyDataset
-                + ", datasets=" + mDatasets
-                + "]";
-    }
-
-    public enum AugmentedResponseType {
-        NORMAL,
-        NULL,
-        TIMEOUT,
-    }
-
-    private Bundle newClientState() {
-        Bundle b = new Bundle();
-        b.putString(CLIENT_STATE_KEY, CLIENT_STATE_VALUE);
-        return b;
-    }
-
-    private FillResponse createResponseWithInlineSuggestion() {
-        List<android.service.autofill.Dataset> list = new ArrayList<>();
-        for (Dataset dataset : mInlineSuggestions) {
-            if (!dataset.getValues().isEmpty()) {
-                android.service.autofill.Dataset.Builder datasetBuilder =
-                        new android.service.autofill.Dataset.Builder();
-                for (Pair<AutofillId, AutofillValue> pair : dataset.getValues()) {
-                    final AutofillId id = pair.first;
-                    datasetBuilder.setFieldInlinePresentation(id, pair.second, null,
-                            dataset.mFieldPresentationById.get(id));
-                    datasetBuilder.setAuthentication(dataset.mAuthentication);
-                }
-                list.add(datasetBuilder.build());
-            }
-        }
-        return new FillResponse.Builder().setInlineSuggestions(list).setClientState(
-                newClientState()).build();
-    }
-
-    public static final class Builder {
-        private final Map<AutofillId, Dataset> mDatasets = new ArrayMap<>();
-        private final AugmentedResponseType mResponseType;
-        private long mDelay;
-        private Dataset mOnlyDataset;
-        private @Nullable List<Dataset> mInlineSuggestions;
-
-        public Builder(@NonNull AugmentedResponseType type) {
-            mResponseType = type;
-        }
-
-        public Builder() {
-            this(AugmentedResponseType.NORMAL);
-        }
-
-        /**
-         * Sets the {@link Dataset} that will be filled when the given {@code ids} is focused and
-         * the UI is tapped.
-         */
-        @NonNull
-        public Builder setDataset(@NonNull Dataset dataset, @NonNull AutofillId... ids) {
-            if (mOnlyDataset != null) {
-                throw new IllegalStateException("already called setOnlyDataset()");
-            }
-            for (AutofillId id : ids) {
-                mDatasets.put(id, dataset);
-            }
-            return this;
-        }
-
-        /**
-         * The {@link android.service.autofill.Dataset}s representing the inline suggestions data.
-         * Defaults to null if no inline suggestions are available from the service.
-         */
-        @NonNull
-        public Builder addInlineSuggestion(@NonNull Dataset dataset) {
-            if (mInlineSuggestions == null) {
-                mInlineSuggestions = new ArrayList<>();
-            }
-            mInlineSuggestions.add(dataset);
-            return this;
-        }
-
-        /**
-         * Sets the delay for onFillRequest().
-         */
-        public Builder setDelay(long delay) {
-            mDelay = delay;
-            return this;
-        }
-
-        /**
-         * Sets the only dataset that will be returned.
-         *
-         * <p>Used when the test case doesn't know the autofill id of the focused field.
-         * @param dataset
-         */
-        @NonNull
-        public Builder setOnlyDataset(@NonNull Dataset dataset) {
-            if (!mDatasets.isEmpty()) {
-                throw new IllegalStateException("already called setDataset()");
-            }
-            mOnlyDataset = dataset;
-            return this;
-        }
-
-        @NonNull
-        public CannedAugmentedFillResponse build() {
-            return new CannedAugmentedFillResponse(this);
-        }
-    } // CannedAugmentedFillResponse.Builder
-
-
-    /**
-     * Helper class used to define which fields will be autofilled when the user taps the Augmented
-     * Autofill UI.
-     */
-    public static class Dataset {
-        private final Map<AutofillId, AutofillValue> mFieldValuesById;
-        private final Map<AutofillId, InlinePresentation> mFieldPresentationById;
-        private final String mPresentation;
-        private final AutofillValue mOnlyFieldValue;
-        private final IntentSender mAuthentication;
-
-        private Dataset(@NonNull Builder builder) {
-            mFieldValuesById = builder.mFieldValuesById;
-            mPresentation = builder.mPresentation;
-            mOnlyFieldValue = builder.mOnlyFieldValue;
-            mFieldPresentationById = builder.mFieldPresentationById;
-            this.mAuthentication = builder.mAuthentication;
-        }
-
-        @NonNull
-        public List<Pair<AutofillId, AutofillValue>> getValues() {
-            return mFieldValuesById.entrySet().stream()
-                    .map((entry) -> (new Pair<>(entry.getKey(), entry.getValue())))
-                    .collect(Collectors.toList());
-        }
-
-        @Nullable
-        public AutofillValue getOnlyFieldValue() {
-            return mOnlyFieldValue;
-        }
-
-        @Override
-        public String toString() {
-            return "Dataset: [presentation=" + mPresentation
-                    + ", onlyField=" + mOnlyFieldValue
-                    + ", fields=" + mFieldValuesById
-                    + ", auth=" + mAuthentication
-                    + "]";
-        }
-
-        public static class Builder {
-            private final Map<AutofillId, AutofillValue> mFieldValuesById = new ArrayMap<>();
-            private final Map<AutofillId, InlinePresentation> mFieldPresentationById =
-                    new ArrayMap<>();
-
-            private final String mPresentation;
-            private AutofillValue mOnlyFieldValue;
-            private IntentSender mAuthentication;
-
-            public Builder(@NonNull String presentation) {
-                mPresentation = Objects.requireNonNull(presentation);
-            }
-
-            /**
-             * Sets the value that will be autofilled on the field with {@code id}.
-             */
-            public Builder setField(@NonNull AutofillId id, @NonNull String text) {
-                if (mOnlyFieldValue != null) {
-                    throw new IllegalStateException("already called setOnlyField()");
-                }
-                mFieldValuesById.put(id, AutofillValue.forText(text));
-                return this;
-            }
-
-            /**
-             * Sets the value that will be autofilled on the field with {@code id}.
-             */
-            public Builder setField(@NonNull AutofillId id, @NonNull String text,
-                    @NonNull InlinePresentation presentation) {
-                if (mOnlyFieldValue != null) {
-                    throw new IllegalStateException("already called setOnlyField()");
-                }
-                mFieldValuesById.put(id, AutofillValue.forText(text));
-                mFieldPresentationById.put(id, presentation);
-                return this;
-            }
-
-            /**
-             * Sets this dataset to return the given {@code text} for the focused field.
-             *
-             * <p>Used when the test case doesn't know the autofill id of the focused field.
-             */
-            public Builder setOnlyField(@NonNull String text) {
-                if (!mFieldValuesById.isEmpty()) {
-                    throw new IllegalStateException("already called setField()");
-                }
-                mOnlyFieldValue = AutofillValue.forText(text);
-                return this;
-            }
-
-            /**
-             * Sets the authentication intent for this dataset.
-             */
-            public Builder setAuthentication(IntentSender authentication) {
-                mAuthentication = authentication;
-                return this;
-            }
-
-            public Dataset build() {
-                return new Dataset(this);
-            }
-        } // Dataset.Builder
-    } // Dataset
-} // CannedAugmentedFillResponse
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/ClipboardAccessTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/ClipboardAccessTest.java
index e002d3e..1a02332 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/ClipboardAccessTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/ClipboardAccessTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.autofillservice.cts.commontests.AugmentedAutofillManualActivityLaunchTestCase;
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.platform.test.annotations.AppModeFull;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java
deleted file mode 100644
index 3604955..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java
+++ /dev/null
@@ -1,443 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.Timeouts.FILL_EVENTS_TIMEOUT;
-import static android.autofillservice.cts.augmented.AugmentedHelper.await;
-import static android.autofillservice.cts.augmented.AugmentedHelper.getActivityName;
-import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_CONNECTION_TIMEOUT;
-import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.AugmentedResponseType.NULL;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.AugmentedResponseType.TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.Helper;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.SystemClock;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.FillEventHistory.Event;
-import android.service.autofill.augmented.AugmentedAutofillService;
-import android.service.autofill.augmented.FillCallback;
-import android.service.autofill.augmented.FillController;
-import android.service.autofill.augmented.FillRequest;
-import android.service.autofill.augmented.FillResponse;
-import android.util.ArraySet;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.compatibility.common.util.RetryableException;
-import com.android.compatibility.common.util.TestNameUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Implementation of {@link AugmentedAutofillService} used in the tests.
- */
-public class CtsAugmentedAutofillService extends AugmentedAutofillService {
-
-    private static final String TAG = CtsAugmentedAutofillService.class.getSimpleName();
-
-    public static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
-    public static final String SERVICE_CLASS = CtsAugmentedAutofillService.class.getSimpleName();
-
-    public static final String SERVICE_NAME = SERVICE_PACKAGE + "/.augmented." + SERVICE_CLASS;
-
-    private static final AugmentedReplier sAugmentedReplier = new AugmentedReplier();
-
-    // We must handle all requests in a separate thread as the service's main thread is the also
-    // the UI thread of the test process and we don't want to hose it in case of failures here
-    private static final HandlerThread sMyThread = new HandlerThread("MyAugmentedServiceThread");
-    private final Handler mHandler;
-
-    private final CountDownLatch mConnectedLatch = new CountDownLatch(1);
-    private final CountDownLatch mDisconnectedLatch = new CountDownLatch(1);
-
-    private static ServiceWatcher sServiceWatcher;
-
-    static {
-        Log.i(TAG, "Starting thread " + sMyThread);
-        sMyThread.start();
-    }
-
-    public CtsAugmentedAutofillService() {
-        mHandler = Handler.createAsync(sMyThread.getLooper());
-    }
-
-    @NonNull
-    public static ServiceWatcher setServiceWatcher() {
-        if (sServiceWatcher != null) {
-            throw new IllegalStateException("There Can Be Only One!");
-        }
-        sServiceWatcher = new ServiceWatcher();
-        return sServiceWatcher;
-    }
-
-
-    public static void resetStaticState() {
-        List<Throwable> exceptions = sAugmentedReplier.mExceptions;
-        if (exceptions != null) {
-            exceptions.clear();
-        }
-        // TODO(b/123540602): should probably set sInstance to null as well, but first we would need
-        // to make sure each test unbinds the service.
-
-        // TODO(b/123540602): each test should use a different service instance, but we need
-        // to provide onConnected() / onDisconnected() methods first and then change the infra so
-        // we can wait for those
-
-        if (sServiceWatcher != null) {
-            Log.wtf(TAG, "resetStaticState(): should not have sServiceWatcher");
-            sServiceWatcher = null;
-        }
-    }
-
-    @Override
-    public void onConnected() {
-        Log.i(TAG, "onConnected(): sServiceWatcher=" + sServiceWatcher);
-
-        if (sServiceWatcher == null) {
-            addException("onConnected() without a watcher");
-            return;
-        }
-
-        if (sServiceWatcher.mService != null) {
-            addException("onConnected(): already created: %s", sServiceWatcher);
-            return;
-        }
-
-        sServiceWatcher.mService = this;
-        sServiceWatcher.mCreated.countDown();
-
-        Log.d(TAG, "Whitelisting " + Helper.MY_PACKAGE + " for augmented autofill");
-        final ArraySet<String> packages = new ArraySet<>(1);
-        packages.add(Helper.MY_PACKAGE);
-
-        final AutofillManager afm = getApplication().getSystemService(AutofillManager.class);
-        if (afm == null) {
-            addException("No AutofillManager on application context on onConnected()");
-            return;
-        }
-        afm.setAugmentedAutofillWhitelist(packages, /* activities= */ null);
-
-        if (mConnectedLatch.getCount() == 0) {
-            addException("already connected: %s", mConnectedLatch);
-        }
-        mConnectedLatch.countDown();
-    }
-
-    @Override
-    public void onDisconnected() {
-        Log.i(TAG, "onDisconnected(): sServiceWatcher=" + sServiceWatcher);
-
-        if (mDisconnectedLatch.getCount() == 0) {
-            addException("already disconnected: %s", mConnectedLatch);
-        }
-        mDisconnectedLatch.countDown();
-
-        if (sServiceWatcher == null) {
-            addException("onDisconnected() without a watcher");
-            return;
-        }
-        if (sServiceWatcher.mService == null) {
-            addException("onDisconnected(): no service on %s", sServiceWatcher);
-            return;
-        }
-
-        sServiceWatcher.mDestroyed.countDown();
-        sServiceWatcher.mService = null;
-        sServiceWatcher = null;
-    }
-
-    public FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
-        return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
-            final FillEventHistory history = getFillEventHistory();
-            if (history == null) {
-                return null;
-            }
-            final List<Event> events = history.getEvents();
-            if (events != null) {
-                assertWithMessage("Didn't get " + expectedSize + " events yet: " + events).that(
-                        events.size()).isEqualTo(expectedSize);
-            } else {
-                assertWithMessage("Events is null (expecting " + expectedSize + ")").that(
-                        expectedSize).isEqualTo(0);
-                return null;
-            }
-            return history;
-        });
-    }
-
-    /**
-     * Waits until the system calls {@link #onConnected()}.
-     */
-    public void waitUntilConnected() throws InterruptedException {
-        await(mConnectedLatch, "not connected");
-    }
-
-    /**
-     * Waits until the system calls {@link #onDisconnected()}.
-     */
-    public void waitUntilDisconnected() throws InterruptedException {
-        await(mDisconnectedLatch, "not disconnected");
-    }
-
-    @Override
-    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
-            FillController controller, FillCallback callback) {
-        Log.i(TAG, "onFillRequest(): " + AugmentedHelper.toString(request));
-
-        final ComponentName component = request.getActivityComponent();
-
-        if (!TestNameUtils.isRunningTest()) {
-            Log.e(TAG, "onFillRequest(" + component + ") called after tests finished");
-            return;
-        }
-        mHandler.post(() -> sAugmentedReplier.handleOnFillRequest(getApplicationContext(), request,
-                cancellationSignal, controller, callback));
-    }
-
-    /**
-     * Gets the {@link AugmentedReplier} singleton.
-     */
-    static AugmentedReplier getAugmentedReplier() {
-        return sAugmentedReplier;
-    }
-
-    private static void addException(@NonNull String fmt, @Nullable Object...args) {
-        final String msg = String.format(fmt, args);
-        Log.e(TAG, msg);
-        sAugmentedReplier.addException(new IllegalStateException(msg));
-    }
-
-    /**
-     * POJO representation of the contents of a {@link FillRequest}
-     * that can be asserted at the end of a test case.
-     */
-    public static final class AugmentedFillRequest {
-        public final FillRequest request;
-        public final CancellationSignal cancellationSignal;
-        public final FillController controller;
-        public final FillCallback callback;
-
-        private AugmentedFillRequest(FillRequest request, CancellationSignal cancellationSignal,
-                FillController controller, FillCallback callback) {
-            this.request = request;
-            this.cancellationSignal = cancellationSignal;
-            this.controller = controller;
-            this.callback = callback;
-        }
-
-        @Override
-        public String toString() {
-            return "AugmentedFillRequest[activity=" + getActivityName(request) + ", request="
-                    + AugmentedHelper.toString(request) + "]";
-        }
-    }
-
-    /**
-     * Object used to answer a
-     * {@link AugmentedAutofillService#onFillRequest(FillRequest, CancellationSignal,
-     * FillController, FillCallback)} on behalf of a unit test method.
-     */
-    public static final class AugmentedReplier {
-
-        private final BlockingQueue<CannedAugmentedFillResponse> mResponses =
-                new LinkedBlockingQueue<>();
-        private final BlockingQueue<AugmentedFillRequest> mFillRequests =
-                new LinkedBlockingQueue<>();
-
-        private List<Throwable> mExceptions;
-        private boolean mReportUnhandledFillRequest = true;
-
-        private AugmentedReplier() {
-        }
-
-        /**
-         * Gets the exceptions thrown asynchronously, if any.
-         */
-        @Nullable
-        public List<Throwable> getExceptions() {
-            return mExceptions;
-        }
-
-        private void addException(@Nullable Throwable e) {
-            if (e == null) return;
-
-            if (mExceptions == null) {
-                mExceptions = new ArrayList<>();
-            }
-            mExceptions.add(e);
-        }
-
-        /**
-         * Sets the expectation for the next {@code onFillRequest}.
-         */
-        public AugmentedReplier addResponse(@NonNull CannedAugmentedFillResponse response) {
-            if (response == null) {
-                throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
-            }
-            mResponses.add(response);
-            return this;
-        }
-        /**
-         * Gets the next fill request, in the order received.
-         */
-        public AugmentedFillRequest getNextFillRequest() {
-            AugmentedFillRequest request;
-            try {
-                request = mFillRequests.poll(AUGMENTED_FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                throw new IllegalStateException("Interrupted", e);
-            }
-            if (request == null) {
-                throw new RetryableException(AUGMENTED_FILL_TIMEOUT, "onFillRequest() not called");
-            }
-            return request;
-        }
-
-        /**
-         * Asserts all {@link AugmentedAutofillService#onFillRequest(FillRequest,
-         * CancellationSignal, FillController, FillCallback)} received by the service were properly
-         * {@link #getNextFillRequest() handled} by the test case.
-         */
-        public void assertNoUnhandledFillRequests() {
-            if (mFillRequests.isEmpty()) return; // Good job, test case!
-
-            if (!mReportUnhandledFillRequest) {
-                // Just log, so it's not thrown again on @After if already thrown on main body
-                Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
-                        + "but logging just in case: " + mFillRequests);
-                return;
-            }
-
-            mReportUnhandledFillRequest = false;
-            throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
-                    + mFillRequests);
-        }
-
-        /**
-         * Gets the current number of unhandled requests.
-         */
-        public int getNumberUnhandledFillRequests() {
-            return mFillRequests.size();
-        }
-
-        /**
-         * Resets its internal state.
-         */
-        public void reset() {
-            mResponses.clear();
-            mFillRequests.clear();
-            mExceptions = null;
-            mReportUnhandledFillRequest = true;
-        }
-
-        private void handleOnFillRequest(@NonNull Context context, @NonNull FillRequest request,
-                @NonNull CancellationSignal cancellationSignal, @NonNull FillController controller,
-                @NonNull FillCallback callback) {
-            final AugmentedFillRequest myRequest = new AugmentedFillRequest(request,
-                    cancellationSignal, controller, callback);
-            Log.d(TAG, "offering " + myRequest);
-            Helper.offer(mFillRequests, myRequest, AUGMENTED_CONNECTION_TIMEOUT.ms());
-            try {
-                final CannedAugmentedFillResponse response;
-                try {
-                    response = mResponses.poll(AUGMENTED_CONNECTION_TIMEOUT.ms(),
-                            TimeUnit.MILLISECONDS);
-                } catch (InterruptedException e) {
-                    Log.w(TAG, "Interrupted getting CannedAugmentedFillResponse: " + e);
-                    Thread.currentThread().interrupt();
-                    addException(e);
-                    return;
-                }
-                if (response == null) {
-                    Log.w(TAG, "onFillRequest() for " + getActivityName(request)
-                            + " received when no canned response was set.");
-                    return;
-                }
-
-                // sleep for timeout tests.
-                final long delay = response.getDelay();
-                if (delay > 0) {
-                    SystemClock.sleep(response.getDelay());
-                }
-
-                if (response.getResponseType() == NULL) {
-                    Log.d(TAG, "onFillRequest(): replying with null");
-                    callback.onSuccess(null);
-                    return;
-                }
-
-                if (response.getResponseType() == TIMEOUT) {
-                    Log.d(TAG, "onFillRequest(): not replying at all");
-                    return;
-                }
-
-                Log.v(TAG, "onFillRequest(): response = " + response);
-                final FillResponse fillResponse = response.asFillResponse(context, request,
-                        controller);
-                Log.v(TAG, "onFillRequest(): fillResponse = " + fillResponse);
-                callback.onSuccess(fillResponse);
-            } catch (Throwable t) {
-                addException(t);
-            }
-        }
-    }
-
-    public static final class ServiceWatcher {
-
-        private final CountDownLatch mCreated = new CountDownLatch(1);
-        private final CountDownLatch mDestroyed = new CountDownLatch(1);
-
-        private CtsAugmentedAutofillService mService;
-
-        @NonNull
-        public CtsAugmentedAutofillService waitOnConnected() throws InterruptedException {
-            await(mCreated, "not created");
-
-            if (mService == null) {
-                throw new IllegalStateException("not created");
-            }
-
-            return mService;
-        }
-
-        public void waitOnDisconnected() throws InterruptedException {
-            await(mDestroyed, "not destroyed");
-        }
-
-        @Override
-        public String toString() {
-            return "mService: " + mService + " created: " + (mCreated.getCount() == 0)
-                    + " destroyed: " + (mDestroyed.getCount() == 0);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/DisableAutofillTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/DisableAutofillTest.java
index 366cf60..4d4b186 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/DisableAutofillTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/DisableAutofillTest.java
@@ -16,13 +16,15 @@
 
 package android.autofillservice.cts.augmented;
 
-import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
-import static android.autofillservice.cts.augmented.AugmentedHelper.resetAugmentedService;
+import static android.autofillservice.cts.testcore.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.AugmentedHelper.resetAugmentedService;
 
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.PreSimpleSaveActivity;
-import android.autofillservice.cts.SimpleSaveActivity;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.autofillservice.cts.activities.PreSimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.commontests.AugmentedAutofillManualActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.uiautomator.UiObject2;
 import android.view.autofill.AutofillId;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractGridActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractGridActivityTestCase.java
new file mode 100644
index 0000000..e23a59d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractGridActivityTestCase.java
@@ -0,0 +1,74 @@
+/*
+ * 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.autofillservice.cts.commontests;
+
+import android.autofillservice.cts.activities.GridActivity;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.autofillservice.cts.testcore.WindowChangeTimeoutException;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Base class for test cases using {@link GridActivity}.
+ */
+public abstract class AbstractGridActivityTestCase
+        extends AutoFillServiceTestCase.AutoActivityLaunch<GridActivity> {
+
+    protected GridActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<GridActivity> getActivityRule() {
+        return new AutofillActivityTestRule<GridActivity>(GridActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+                postActivityLaunched();
+            }
+        };
+    }
+
+    /**
+     * Hook for subclass to customize activity after it's launched.
+     */
+    protected void postActivityLaunched() {
+    }
+
+    /**
+     * Focus to a cell and expect window event
+     */
+    protected void focusCell(int row, int column) throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column));
+    }
+
+    /**
+     * Focus to a cell and expect no window event.
+     */
+    protected void focusCellNoWindowChange(int row, int column) {
+        final AccessibilityEvent event;
+        try {
+            event = mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column),
+                    Timeouts.WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS);
+        } catch (WindowChangeTimeoutException ex) {
+            // no window events! looking good
+            return;
+        }
+        throw new IllegalStateException(String.format("Expect no window event when focusing to"
+                + " column %d row %d, but event happened: %s", row, column, event));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginActivityTestCase.java
new file mode 100644
index 0000000..33e99a1
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginActivityTestCase.java
@@ -0,0 +1,91 @@
+/*
+ * 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.autofillservice.cts.commontests;
+
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.autofillservice.cts.testcore.UiBot;
+import android.autofillservice.cts.testcore.WindowChangeTimeoutException;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Base class for test cases using {@link LoginActivity}.
+ */
+public abstract class AbstractLoginActivityTestCase
+        extends AutoFillServiceTestCase.AutoActivityLaunch<LoginActivity> {
+
+    protected LoginActivity mActivity;
+
+    protected AbstractLoginActivityTestCase() {
+    }
+
+    protected AbstractLoginActivityTestCase(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<LoginActivity> getActivityRule() {
+        return new AutofillActivityTestRule<LoginActivity>(
+                LoginActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    /**
+     * Requests focus on username and expect Window event happens.
+     */
+    protected void requestFocusOnUsername() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus));
+    }
+
+    /**
+     * Requests focus on username and expect no Window event happens.
+     */
+    protected void requestFocusOnUsernameNoWindowChange() {
+        final AccessibilityEvent event;
+        try {
+            event = mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus),
+                    Timeouts.WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS);
+        } catch (WindowChangeTimeoutException ex) {
+            // no window events! looking good
+            return;
+        }
+        throw new IllegalStateException("Expect no window event when focusing to"
+                + " username, but event happened: " + event);
+    }
+
+    /**
+     * Requests focus on password and expect Window event happens.
+     */
+    protected void requestFocusOnPassword() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.onPassword(View::requestFocus));
+    }
+
+    /**
+     * Clears focus from input fields by focusing on the parent layout.
+     */
+    protected void clearFocus() {
+        mActivity.clearFocus();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginNotImportantForAutofillTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginNotImportantForAutofillTestCase.java
new file mode 100644
index 0000000..eb5b87a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginNotImportantForAutofillTestCase.java
@@ -0,0 +1,234 @@
+/*
+ * 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.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.testcore.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+
+import android.autofillservice.cts.activities.LoginNotImportantForAutofillActivity;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.EditText;
+
+import org.junit.Test;
+
+public abstract class AbstractLoginNotImportantForAutofillTestCase<A extends
+        LoginNotImportantForAutofillActivity> extends
+        AugmentedAutofillAutoActivityLaunchTestCase<A> {
+
+    protected A mActivity;
+
+    @Test
+    public void testAutofill_none() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        final AutofillId expectedFocusedId = username.getAutofillId();
+        sAugmentedReplier.addResponse(NO_AUGMENTED_RESPONSE);
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request, mActivity, expectedFocusedId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is not shown.
+        mAugmentedUiBot.assertUiNeverShown();
+    }
+
+    @Test
+    public void testAutofill_oneField() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude")
+                        .build(), usernameId)
+                .build());
+        mActivity.expectAutoFill("dude");
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
+
+        // Autofill
+        ui.click();
+        mActivity.assertAutoFilled();
+        mAugmentedUiBot.assertUiGone();
+    }
+
+    @Test
+    public void testAutofill_twoFields() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude")
+                        .setField(mActivity.getPassword().getAutofillId(), "sweet")
+                        .build(), usernameId)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
+
+        // Autofill
+        ui.click();
+        mActivity.assertAutoFilled();
+        mAugmentedUiBot.assertUiGone();
+    }
+
+    @Test
+    public void testAutofill_manualRequest() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude")
+                        .build(), usernameId)
+                .build());
+        mActivity.expectAutoFill("dude");
+
+        // Trigger autofill
+        mActivity.forceAutofillOnUsername();
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        // No inline request because didn't focus on any view.
+        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue,
+                /* hasInlineRequest */ false);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
+
+        // Autofill
+        ui.click();
+        mActivity.assertAutoFilled();
+        mAugmentedUiBot.assertUiGone();
+    }
+
+    @Test
+    public void testAutofill_autoThenManualRequests() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "WHATEVER")
+                        .build(), usernameId)
+                .build());
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        final AugmentedFillRequest request1 = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request1, mActivity, usernameId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
+
+        sReplier.addResponse(NO_RESPONSE);
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Fill Me")
+                        .setField(usernameId, "dude")
+                        .build(), usernameId)
+                .build());
+        mActivity.expectAutoFill("dude");
+
+        // Trigger autofill
+        mActivity.clearFocus();
+        mActivity.forceAutofillOnUsername();
+        sReplier.getNextFillRequest();
+        final AugmentedFillRequest request2 = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request2, mActivity, usernameId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Fill Me");
+
+        // Autofill
+        ui.click();
+        mActivity.assertAutoFilled();
+        mAugmentedUiBot.assertUiGone();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractWebViewTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractWebViewTestCase.java
new file mode 100644
index 0000000..7720bc8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractWebViewTestCase.java
@@ -0,0 +1,48 @@
+/*
+ * 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.autofillservice.cts.commontests;
+
+import android.autofillservice.cts.activities.AbstractWebViewActivity;
+import android.autofillservice.cts.testcore.IdMode;
+import android.autofillservice.cts.testcore.UiBot;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public abstract class AbstractWebViewTestCase<A extends AbstractWebViewActivity>
+        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    protected AbstractWebViewTestCase() {
+    }
+
+    protected AbstractWebViewTestCase(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
+    // TODO(b/64951517): WebView currently does not trigger the autofill callbacks when values are
+    // set using accessibility.
+    protected static final boolean INJECT_EVENTS = true;
+
+    @BeforeClass
+    public static void setReplierMode() {
+        sReplier.setIdMode(IdMode.HTML_NAME);
+    }
+
+    @AfterClass
+    public static void resetReplierMode() {
+        sReplier.setIdMode(IdMode.RESOURCE_ID);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillAutoActivityLaunchTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillAutoActivityLaunchTestCase.java
new file mode 100644
index 0000000..851bcb7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillAutoActivityLaunchTestCase.java
@@ -0,0 +1,125 @@
+/*
+ * 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.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.testcore.Helper.allowOverlays;
+import static android.autofillservice.cts.testcore.Helper.disallowOverlays;
+
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+import android.autofillservice.cts.testcore.AugmentedHelper;
+import android.autofillservice.cts.testcore.AugmentedUiBot;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedReplier;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.AutofillOptions;
+import android.view.autofill.AutofillManager;
+
+import com.android.compatibility.common.util.RequiredSystemResourceRule;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+/////
+///// NOTE: changes in this class should also be applied to
+/////       AugmentedAutofillManualActivityLaunchTestCase, which is exactly the same as this except
+/////       by which class it extends.
+
+// Must be public because of the @ClassRule
+public abstract class AugmentedAutofillAutoActivityLaunchTestCase
+        <A extends AbstractAutoFillActivity> extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    protected static AugmentedReplier sAugmentedReplier;
+    protected AugmentedUiBot mAugmentedUiBot;
+
+    private CtsAugmentedAutofillService.ServiceWatcher mServiceWatcher;
+
+    private static final RequiredSystemResourceRule sRequiredResource =
+            new RequiredSystemResourceRule("config_defaultAugmentedAutofillService");
+
+    private static final RuleChain sRequiredFeatures = RuleChain
+            .outerRule(sRequiredFeatureRule)
+            .around(sRequiredResource);
+
+    public AugmentedAutofillAutoActivityLaunchTestCase() {}
+
+    public AugmentedAutofillAutoActivityLaunchTestCase(UiBot uiBot) {
+        super(uiBot);
+    }
+
+    @BeforeClass
+    public static void allowAugmentedAutofill() {
+        sContext.getApplicationContext()
+                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
+        allowOverlays();
+    }
+
+    @AfterClass
+    public static void resetAllowAugmentedAutofill() {
+        sContext.getApplicationContext().setAutofillOptions(null);
+        disallowOverlays();
+    }
+
+    @Before
+    public void setFixtures() {
+        mServiceWatcher = null;
+        sAugmentedReplier = CtsAugmentedAutofillService.getAugmentedReplier();
+        sAugmentedReplier.reset();
+        CtsAugmentedAutofillService.resetStaticState();
+        mAugmentedUiBot = new AugmentedUiBot(mUiBot);
+        mSafeCleanerRule
+                .run(() -> sAugmentedReplier.assertNoUnhandledFillRequests())
+                .run(() -> {
+                    AugmentedHelper.resetAugmentedService();
+                    if (mServiceWatcher != null) {
+                        mServiceWatcher.waitOnDisconnected();
+                    }
+                })
+                .add(() -> {
+                    return sAugmentedReplier.getExceptions();
+                });
+    }
+
+    @Override
+    protected int getNumberRetries() {
+        return 0; // A.K.A. "Optimistic Thinking"
+    }
+
+    @Override
+    protected int getSmartSuggestionMode() {
+        return AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
+    }
+
+    @Override
+    protected TestRule getRequiredFeaturesRule() {
+        return sRequiredFeatures;
+    }
+
+    protected CtsAugmentedAutofillService enableAugmentedService() throws InterruptedException {
+        if (mServiceWatcher != null) {
+            throw new IllegalStateException("There Can Be Only One!");
+        }
+
+        mServiceWatcher = CtsAugmentedAutofillService.setServiceWatcher();
+        AugmentedHelper.setAugmentedService(CtsAugmentedAutofillService.SERVICE_NAME);
+
+        CtsAugmentedAutofillService service = mServiceWatcher.waitOnConnected();
+        service.waitUntilConnected();
+        return service;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillManualActivityLaunchTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillManualActivityLaunchTestCase.java
new file mode 100644
index 0000000..c2517ee
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillManualActivityLaunchTestCase.java
@@ -0,0 +1,116 @@
+/*
+ * 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.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.testcore.Helper.allowOverlays;
+import static android.autofillservice.cts.testcore.Helper.disallowOverlays;
+
+import android.autofillservice.cts.testcore.AugmentedHelper;
+import android.autofillservice.cts.testcore.AugmentedUiBot;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedReplier;
+import android.content.AutofillOptions;
+import android.view.autofill.AutofillManager;
+
+import com.android.compatibility.common.util.RequiredSystemResourceRule;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+/////
+///// NOTE: changes in this class should also be applied to
+/////       AugmentedAutofillManualActivityLaunchTestCase, which is exactly the same as this except
+/////       by which class it extends.
+
+// Must be public because of the @ClassRule
+public abstract class AugmentedAutofillManualActivityLaunchTestCase
+        extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+    protected static AugmentedReplier sAugmentedReplier;
+    protected AugmentedUiBot mAugmentedUiBot;
+
+    private CtsAugmentedAutofillService.ServiceWatcher mServiceWatcher;
+
+    private static final RequiredSystemResourceRule sRequiredResource =
+            new RequiredSystemResourceRule("config_defaultAugmentedAutofillService");
+
+    private static final RuleChain sRequiredFeatures = RuleChain
+            .outerRule(sRequiredFeatureRule)
+            .around(sRequiredResource);
+
+    @BeforeClass
+    public static void allowAugmentedAutofill() {
+        sContext.getApplicationContext()
+                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
+        allowOverlays();
+    }
+
+    @AfterClass
+    public static void resetAllowAugmentedAutofill() {
+        sContext.getApplicationContext().setAutofillOptions(null);
+        disallowOverlays();
+    }
+
+    @Before
+    public void setFixtures() {
+        sAugmentedReplier = CtsAugmentedAutofillService.getAugmentedReplier();
+        sAugmentedReplier.reset();
+        CtsAugmentedAutofillService.resetStaticState();
+        mAugmentedUiBot = new AugmentedUiBot(mUiBot);
+        mSafeCleanerRule
+                .run(() -> sAugmentedReplier.assertNoUnhandledFillRequests())
+                .run(() -> {
+                    AugmentedHelper.resetAugmentedService();
+                    if (mServiceWatcher != null) {
+                        mServiceWatcher.waitOnDisconnected();
+                    }
+                })
+                .add(() -> {
+                    return sAugmentedReplier.getExceptions();
+                });
+    }
+
+    @Override
+    protected int getSmartSuggestionMode() {
+        return AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
+    }
+
+    @Override
+    protected TestRule getRequiredFeaturesRule() {
+        return sRequiredFeatures;
+    }
+
+    protected CtsAugmentedAutofillService enableAugmentedService() throws InterruptedException {
+        return enableAugmentedService(/* whitelistSelf= */ true);
+    }
+
+    protected CtsAugmentedAutofillService enableAugmentedService(boolean whitelistSelf)
+            throws InterruptedException {
+        if (mServiceWatcher != null) {
+            throw new IllegalStateException("There Can Be Only One!");
+        }
+
+        mServiceWatcher = CtsAugmentedAutofillService.setServiceWatcher();
+        AugmentedHelper.setAugmentedService(CtsAugmentedAutofillService.SERVICE_NAME);
+
+        CtsAugmentedAutofillService service = mServiceWatcher.waitOnConnected();
+        service.waitUntilConnected();
+        return service;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
new file mode 100644
index 0000000..1ca23be
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.SERVICE_NAME;
+import static android.content.Context.CLIPBOARD_SERVICE;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import android.app.PendingIntent;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+import android.autofillservice.cts.activities.AugmentedAuthActivity;
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.activities.PreSimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.AutofillLoggingTestRule;
+import android.autofillservice.cts.testcore.AutofillTestWatcher;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.Replier;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.service.autofill.InlinePresentation;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
+import com.android.compatibility.common.util.RequiredFeatureRule;
+import com.android.compatibility.common.util.RetryRule;
+import com.android.compatibility.common.util.SafeCleanerRule;
+import com.android.compatibility.common.util.SettingsStateKeeperRule;
+import com.android.compatibility.common.util.TestNameUtils;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSessionRule;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+
+/**
+ * Placeholder for the base class for all integration tests:
+ *
+ * <ul>
+ *   <li>{@link AutoActivityLaunch}
+ *   <li>{@link ManualActivityLaunch}
+ * </ul>
+ *
+ * <p>These classes provide the common infrastructure such as:
+ *
+ * <ul>
+ *   <li>Preserving the autofill service settings.
+ *   <li>Cleaning up test state.
+ *   <li>Wrapping the test under autofill-specific test rules.
+ *   <li>Launching the activity used by the test.
+ * </ul>
+ */
+public final class AutoFillServiceTestCase {
+
+    /**
+     * Base class for all test cases that use an {@link AutofillActivityTestRule} to
+     * launch the activity.
+     */
+    // Must be public because of @ClassRule
+    public abstract static class AutoActivityLaunch<A extends AbstractAutoFillActivity>
+            extends BaseTestCase {
+
+        /**
+         * Returns if inline suggestion is enabled.
+         */
+        protected boolean isInlineMode() {
+            return false;
+        }
+
+        protected static UiBot getInlineUiBot() {
+            return sDefaultUiBot2;
+        }
+
+        protected static UiBot getDropdownUiBot() {
+            return sDefaultUiBot;
+        }
+
+        @ClassRule
+        public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
+                sTheRealServiceSettingsKeeper;
+
+        protected AutoActivityLaunch() {
+            super(sDefaultUiBot);
+        }
+        protected AutoActivityLaunch(UiBot uiBot) {
+            super(uiBot);
+        }
+
+        @Override
+        protected TestRule getMainTestRule() {
+            return getActivityRule();
+        }
+
+        /**
+         * Gets the rule to launch the main activity for this test.
+         *
+         * <p><b>Note: </b>the rule must be either lazily generated or a static singleton, otherwise
+         * this method could return {@code null} when the rule chain that uses it is constructed.
+         *
+         */
+        protected abstract @NonNull AutofillActivityTestRule<A> getActivityRule();
+
+        protected @NonNull A launchActivity(@NonNull Intent intent) {
+            return getActivityRule().launchActivity(intent);
+        }
+
+        protected @NonNull A getActivity() {
+            return getActivityRule().getActivity();
+        }
+    }
+
+    /**
+     * Base class for all test cases that don't require an {@link AutofillActivityTestRule}.
+     */
+    // Must be public because of @ClassRule
+    public abstract static class ManualActivityLaunch extends BaseTestCase {
+
+        @ClassRule
+        public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
+                sTheRealServiceSettingsKeeper;
+
+        protected ManualActivityLaunch() {
+            this(sDefaultUiBot);
+        }
+
+        protected ManualActivityLaunch(@NonNull UiBot uiBot) {
+            super(uiBot);
+        }
+
+        @Override
+        protected TestRule getMainTestRule() {
+            // TODO: create a NoOpTestRule on common code
+            return new TestRule() {
+
+                @Override
+                public Statement apply(Statement base, Description description) {
+                    // Returns a no-op statements
+                    return new Statement() {
+                        @Override
+                        public void evaluate() throws Throwable {
+                            base.evaluate();
+                        }
+                    };
+                }
+            };
+        }
+
+        protected SimpleSaveActivity startSimpleSaveActivity() throws Exception {
+            final Intent intent = new Intent(mContext, SimpleSaveActivity.class)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mContext.startActivity(intent);
+            mUiBot.assertShownByRelativeId(SimpleSaveActivity.ID_LABEL);
+            return SimpleSaveActivity.getInstance();
+        }
+
+        protected PreSimpleSaveActivity startPreSimpleSaveActivity() throws Exception {
+            final Intent intent = new Intent(mContext, PreSimpleSaveActivity.class)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mContext.startActivity(intent);
+            mUiBot.assertShownByRelativeId(PreSimpleSaveActivity.ID_PRE_LABEL);
+            return PreSimpleSaveActivity.getInstance();
+        }
+    }
+
+    @RunWith(AndroidJUnit4.class)
+    // Must be public because of @ClassRule
+    public abstract static class BaseTestCase {
+
+        private static final String TAG = "AutoFillServiceTestCase";
+
+        protected static final Replier sReplier = InstrumentedAutoFillService.getReplier();
+
+        protected static final Context sContext = getInstrumentation().getTargetContext();
+
+        // Hack because JUnit requires that @ClassRule instance belong to a public class.
+        protected static final SettingsStateKeeperRule sTheRealServiceSettingsKeeper =
+                new SettingsStateKeeperRule(sContext, Settings.Secure.AUTOFILL_SERVICE) {
+            @Override
+            protected void preEvaluate(Description description) {
+                TestNameUtils.setCurrentTestClass(description.getClassName());
+            }
+
+            @Override
+            protected void postEvaluate(Description description) {
+                TestNameUtils.setCurrentTestClass(null);
+            }
+        };
+
+        public static final MockImeSessionRule sMockImeSessionRule = new MockImeSessionRule(
+                InstrumentationRegistry.getTargetContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setInlineSuggestionsEnabled(true)
+                        .setInlineSuggestionViewContentDesc(InlineUiBot.SUGGESTION_STRIP_DESC));
+
+        protected static final RequiredFeatureRule sRequiredFeatureRule =
+                new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
+
+        private final AutofillTestWatcher mTestWatcher = new AutofillTestWatcher();
+
+        private final RetryRule mRetryRule =
+                new RetryRule(getNumberRetries(), () -> {
+                    // Between testing and retries, clean all launched activities to avoid
+                    // exception:
+                    //     Could not launch intent Intent { ... } within 45 seconds.
+                    mTestWatcher.cleanAllActivities();
+                });
+
+        private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
+
+        protected final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
+                .setDumper(mLoggingRule)
+                .run(() -> sReplier.assertNoUnhandledFillRequests())
+                .run(() -> sReplier.assertNoUnhandledSaveRequests())
+                .add(() -> {
+                    return sReplier.getExceptions();
+                });
+
+        @Rule
+        public final RuleChain mLookAllTheseRules = RuleChain
+                //
+                // requiredFeatureRule should be first so the test can be skipped right away
+                .outerRule(getRequiredFeaturesRule())
+                //
+                // mTestWatcher should always be one the first rules, as it defines the name of the
+                // test being ran and finishes dangling activities at the end
+                .around(mTestWatcher)
+                //
+                // sMockImeSessionRule make sure MockImeSession.create() is used to launch mock IME
+                .around(sMockImeSessionRule)
+                //
+                // mLoggingRule wraps the test but doesn't interfere with it
+                .around(mLoggingRule)
+                //
+                // mSafeCleanerRule will catch errors
+                .around(mSafeCleanerRule)
+                //
+                // mRetryRule should be closest to the main test as possible
+                .around(mRetryRule)
+                //
+                // Augmented Autofill should be disabled by default
+                .around(new DeviceConfigStateChangerRule(sContext, DeviceConfig.NAMESPACE_AUTOFILL,
+                        AutofillManager.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES,
+                        Integer.toString(getSmartSuggestionMode())))
+                //
+                // Finally, let subclasses add their own rules (like ActivityTestRule)
+                .around(getMainTestRule());
+
+
+        protected final Context mContext = sContext;
+        protected final String mPackageName;
+        protected final UiBot mUiBot;
+
+        private BaseTestCase(@NonNull UiBot uiBot) {
+            mPackageName = mContext.getPackageName();
+            mUiBot = uiBot;
+            mUiBot.reset();
+        }
+
+        protected int getSmartSuggestionMode() {
+            return AutofillManager.FLAG_SMART_SUGGESTION_OFF;
+        }
+
+        /**
+         * Gets how many times a test should be retried.
+         *
+         * @return {@code 1} by default, unless overridden by subclasses or by a global settings
+         * named {@code CLASS_NAME + #getNumberRetries} or
+         * {@code CtsAutoFillServiceTestCases#getNumberRetries} (the former having a higher
+         * priority).
+         */
+        protected int getNumberRetries() {
+            final String localProp = getClass().getName() + "#getNumberRetries";
+            final Integer localValue = getNumberRetries(localProp);
+            if (localValue != null) return localValue.intValue();
+
+            final String globalProp = "CtsAutoFillServiceTestCases#getNumberRetries";
+            final Integer globalValue = getNumberRetries(globalProp);
+            if (globalValue != null) return globalValue.intValue();
+
+            return 1;
+        }
+
+        private Integer getNumberRetries(String prop) {
+            final String value = Settings.Global.getString(sContext.getContentResolver(), prop);
+            if (value != null) {
+                Log.i(TAG, "getNumberRetries(): overriding to " + value + " because of '" + prop
+                        + "' global setting");
+                try {
+                    return Integer.parseInt(value);
+                } catch (Exception e) {
+                    Log.w(TAG, "error parsing property '" + prop + "'='" + value + "'", e);
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Gets a rule that defines which features must be present for this test to run.
+         *
+         * <p>By default it returns a rule that requires {@link PackageManager#FEATURE_AUTOFILL},
+         * but subclass can override to be more specific.
+         */
+        @NonNull
+        protected TestRule getRequiredFeaturesRule() {
+            return sRequiredFeatureRule;
+        }
+
+        /**
+         * Gets the test-specific {@link Rule @Rule}.
+         *
+         * <p>Sub-class <b>MUST</b> override this method instead of annotation their own rules,
+         * so the order is preserved.
+         *
+         */
+        @NonNull
+        protected abstract TestRule getMainTestRule();
+
+        @BeforeClass
+        public static void disableDefaultAugmentedService() {
+            Log.v(TAG, "@BeforeClass: disableDefaultAugmentedService()");
+            Helper.setDefaultAugmentedAutofillServiceEnabled(false);
+        }
+
+        @AfterClass
+        public static void enableDefaultAugmentedService() {
+            Log.v(TAG, "@AfterClass: enableDefaultAugmentedService()");
+            Helper.setDefaultAugmentedAutofillServiceEnabled(true);
+        }
+
+        @Before
+        public void prepareDevice() throws Exception {
+            Log.v(TAG, "@Before: prepareDevice()");
+
+            // Unlock screen.
+            runShellCommand("input keyevent KEYCODE_WAKEUP");
+
+            // Dismiss keyguard, in case it's set as "Swipe to unlock".
+            runShellCommand("wm dismiss-keyguard");
+
+            // Collapse notifications.
+            runShellCommand("cmd statusbar collapse");
+
+            // Set orientation as portrait, otherwise some tests might fail due to elements not
+            // fitting in, IME orientation, etc...
+            mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+
+            // Wait until device is idle to avoid flakiness
+            mUiBot.waitForIdle();
+
+            // Clear Clipboard
+            // TODO(b/117768051): remove try/catch once fixed
+            try {
+                ((ClipboardManager) mContext.getSystemService(CLIPBOARD_SERVICE))
+                    .clearPrimaryClip();
+            } catch (Exception e) {
+                Log.e(TAG, "Ignoring exception clearing clipboard", e);
+            }
+        }
+
+        @Before
+        public void preTestCleanup() {
+            Log.v(TAG, "@Before: preTestCleanup()");
+
+            prepareServicePreTest();
+
+            InstrumentedAutoFillService.resetStaticState();
+            AuthenticationActivity.resetStaticState();
+            AugmentedAuthActivity.resetStaticState();
+            sReplier.reset();
+        }
+
+        /**
+         * Prepares the service before each test - by default, disables it
+         */
+        protected void prepareServicePreTest() {
+            Log.v(TAG, "prepareServicePreTest(): calling disableService()");
+            disableService();
+        }
+
+        /**
+         * Enables the {@link InstrumentedAutoFillService} for autofill for the current user.
+         */
+        protected void enableService() {
+            Helper.enableAutofillService(getContext(), SERVICE_NAME);
+        }
+
+        /**
+         * Disables the {@link InstrumentedAutoFillService} for autofill for the current user.
+         */
+        protected void disableService() {
+            Helper.disableAutofillService(getContext());
+        }
+
+        /**
+         * Asserts that the {@link InstrumentedAutoFillService} is enabled for the default user.
+         */
+        protected void assertServiceEnabled() {
+            Helper.assertAutofillServiceStatus(SERVICE_NAME, true);
+        }
+
+        /**
+         * Asserts that the {@link InstrumentedAutoFillService} is disabled for the default user.
+         */
+        protected void assertServiceDisabled() {
+            Helper.assertAutofillServiceStatus(SERVICE_NAME, false);
+        }
+
+        protected RemoteViews createPresentation(String message) {
+            return Helper.createPresentation(message);
+        }
+
+        protected RemoteViews createPresentationWithCancel(String message) {
+            final RemoteViews presentation = new RemoteViews(getContext()
+                    .getPackageName(), R.layout.list_item_cancel);
+            presentation.setTextViewText(R.id.text1, message);
+            return presentation;
+        }
+
+        protected InlinePresentation createInlinePresentation(String message) {
+            return Helper.createInlinePresentation(message);
+        }
+
+        protected InlinePresentation createInlinePresentation(String message,
+                                                              PendingIntent attribution) {
+            return Helper.createInlinePresentation(message, attribution);
+        }
+
+        @NonNull
+        protected AutofillManager getAutofillManager() {
+            return mContext.getSystemService(AutofillManager.class);
+        }
+    }
+
+    protected static final UiBot sDefaultUiBot = new UiBot();
+    protected static final UiBot sDefaultUiBot2 = new InlineUiBot();
+
+    private AutoFillServiceTestCase() {
+        throw new UnsupportedOperationException("Contain static stuff only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/CustomDescriptionWithLinkTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/CustomDescriptionWithLinkTestCase.java
new file mode 100644
index 0000000..a79fc9d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/CustomDescriptionWithLinkTestCase.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.commontests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Intent;
+import android.service.autofill.CustomDescription;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+/**
+ * Template for tests cases that test what happens when a link in the {@link CustomDescription} is
+ * tapped by the user.
+ *
+ * <p>It must be extend by 2 sub-class to provide tests for the 2 distinct scenarios:
+ * <ul>
+ *   <li>Save is triggered by 1st activity finishing and launching a 2nd activity.
+ *   <li>Save is triggered by explicit {@link android.view.autofill.AutofillManager#commit()} call
+ *       and shown in the same activity.
+ * </ul>
+ *
+ * <p>The overall behavior should be the same in both cases, although the implementation of the
+ * tests per se will be sligthly different.
+ */
+public abstract class CustomDescriptionWithLinkTestCase<A extends AbstractAutoFillActivity> extends
+        AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    private static final String ID_LINK = "link";
+
+    private final Class<A> mActivityClass;
+
+    protected A mActivity;
+
+    protected CustomDescriptionWithLinkTestCase(@NonNull Class<A> activityClass) {
+        mActivityClass = activityClass;
+    }
+
+    protected void startActivity() {
+        startActivity(false);
+    }
+
+    protected void startActivity(boolean remainOnRecents) {
+        final Intent intent = new Intent(mContext, mActivityClass);
+        if (remainOnRecents) {
+            intent.setFlags(
+                    Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK);
+        }
+        mActivity = launchActivity(intent);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description and then taps back:
+     * the Save UI should have been restored.
+     */
+    @Test
+    public final void testTapLink_tapBack() throws Exception {
+        saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction.TAP_BACK_BUTTON);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, change the screen
+     * orientation while the new activity is show, then taps back:
+     * the Save UI should have been restored.
+     */
+    @Test
+    public final void testTapLink_changeOrientationThenTapBack() throws Exception {
+        assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
+
+        mUiBot.assumeMinimumResolution(500);
+        mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+        try {
+            saveUiRestoredAfterTappingLinkTest(
+                    PostSaveLinkTappedAction.ROTATE_THEN_TAP_BACK_BUTTON);
+        } finally {
+            try {
+                mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+                cleanUpAfterScreenOrientationIsBackToPortrait();
+            } catch (Exception e) {
+                mSafeCleanerRule.add(e);
+            } finally {
+                mUiBot.resetScreenResolution();
+            }
+        }
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, then the new activity
+     * finishes:
+     * the Save UI should have been restored.
+     */
+    @Test
+    public final void testTapLink_finishActivity() throws Exception {
+        saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction.FINISH_ACTIVITY);
+    }
+
+    protected abstract void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception;
+
+    protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and touch outside the Save UI to dismiss it.
+     *
+     * <p>Then user starts a new session by focusing in a field.
+     */
+    @Test
+    public final void testTapLink_tapBack_thenStartOverByTouchOutsideAndFocus()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TOUCH_OUTSIDE, false);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and touch outside the Save UI to dismiss it.
+     *
+     * <p>Then user starts a new session by forcing autofill.
+     */
+    @Test
+    public void testTapLink_tapBack_thenStartOverByTouchOutsideAndManualRequest()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TOUCH_OUTSIDE, true);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and tap the "No" button to dismiss it.
+     *
+     * <p>Then user starts a new session by focusing in a field.
+     */
+    @Test
+    public final void testTapLink_tapBack_thenStartOverBySayingNoAndFocus()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_NO_ON_SAVE_UI,
+                false);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and tap the "No" button to dismiss it.
+     *
+     * <p>Then user starts a new session by forcing autofill.
+     */
+    @Test
+    public final void testTapLink_tapBack_thenStartOverBySayingNoAndManualRequest()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_NO_ON_SAVE_UI, true);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and the "Yes" button to save it.
+     *
+     * <p>Then user starts a new session by focusing in a field.
+     */
+    @Test
+    public final void testTapLink_tapBack_thenStartOverBySayingYesAndFocus()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_YES_ON_SAVE_UI,
+                false);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and the "Yes" button to save it.
+     *
+     * <p>Then user starts a new session by forcing autofill.
+     */
+    @Test
+    public final void testTapLink_tapBack_thenStartOverBySayingYesAndManualRequest()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_YES_ON_SAVE_UI, true);
+    }
+
+    protected abstract void tapLinkThenTapBackThenStartOverTest(
+            PostSaveLinkTappedAction action, boolean manualRequest) throws Exception;
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, then re-launches the
+     * original activity:
+     * the Save UI should have been canceled.
+     */
+    @Test
+    public final void testTapLink_backToPreviousActivityByLaunchingIt()
+            throws Exception {
+        saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_PREVIOUS_ACTIVITY);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, then launches a 3rd
+     * activity:
+     * the Save UI should have been canceled.
+     */
+    @Test
+    public final void testTapLink_launchNewActivityThenTapBack() throws Exception {
+        saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_NEW_ACTIVITY);
+    }
+
+    protected abstract void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception;
+
+    @Test
+    public final void testTapLink_launchTrampolineActivityThenTapBackAndStartNewSession()
+            throws Exception {
+        // Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist.
+        Helper.resetApplicationAutofillOptions(sContext);
+
+        tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest();
+    }
+
+    protected abstract void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
+            throws Exception;
+
+    @Test
+    public final void testTapLinkAfterUpdateAppliedToLinkView() throws Exception {
+        tapLinkAfterUpdateAppliedTest(true);
+    }
+
+    @Test
+    public final void testTapLinkAfterUpdateAppliedToAnotherView() throws Exception {
+        tapLinkAfterUpdateAppliedTest(false);
+    }
+
+    protected abstract void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception;
+
+    public enum PostSaveLinkTappedAction {
+        TAP_BACK_BUTTON,
+        ROTATE_THEN_TAP_BACK_BUTTON,
+        FINISH_ACTIVITY,
+        LAUNCH_NEW_ACTIVITY,
+        LAUNCH_PREVIOUS_ACTIVITY,
+        TOUCH_OUTSIDE,
+        TAP_NO_ON_SAVE_UI,
+        TAP_YES_ON_SAVE_UI
+    }
+
+    protected final void startActivityOnNewTask(Class<?> clazz) {
+        final Intent intent = new Intent(mContext, clazz);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
+    protected RemoteViews newTemplate() {
+        final RemoteViews presentation = new RemoteViews(mPackageName,
+                R.layout.custom_description_with_link);
+        return presentation;
+    }
+
+    protected final CustomDescription.Builder newCustomDescriptionBuilder(
+            Class<? extends Activity> activityClass) {
+        final Intent intent = new Intent(mContext, activityClass);
+        intent.setFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+        return newCustomDescriptionBuilder(intent);
+    }
+
+    protected final CustomDescription newCustomDescription(
+            Class<? extends Activity> activityClass) {
+        return newCustomDescriptionBuilder(activityClass).build();
+    }
+
+    protected final CustomDescription.Builder newCustomDescriptionBuilder(Intent intent) {
+        final RemoteViews presentation = newTemplate();
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        presentation.setOnClickPendingIntent(R.id.link, pendingIntent);
+        return new CustomDescription.Builder(presentation);
+    }
+
+    protected final CustomDescription newCustomDescription(Intent intent) {
+        return newCustomDescriptionBuilder(intent).build();
+    }
+
+    protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType) throws Exception {
+        return assertSaveUiWithLinkIsShown(saveType, "DON'T TAP ME!");
+    }
+
+    protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType, String expectedText)
+            throws Exception {
+        // First make sure the UI is shown...
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(saveType);
+        // Then make sure it does have the custom view with link on it...
+        final UiObject2 link = getLink(saveUi);
+        assertThat(link.getText()).isEqualTo(expectedText);
+        return saveUi;
+    }
+
+    protected final UiObject2 getLink(final UiObject2 container) {
+        final UiObject2 link = container.findObject(By.res(mPackageName, ID_LINK));
+        assertThat(link).isNotNull();
+        return link;
+    }
+
+    protected final void tapSaveUiLink(UiObject2 saveUi) {
+        getLink(saveUi).click();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/DatasetFilteringTest.java
new file mode 100644
index 0000000..6b0e4de
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/DatasetFilteringTest.java
@@ -0,0 +1,676 @@
+/*
+ * 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.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Timeouts.MOCK_IME_TIMEOUT_MS;
+
+import static com.android.compatibility.common.util.ShellUtils.sendKeyEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.MaxVisibleDatasetsRule;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.IntentSender;
+import android.os.Process;
+import android.platform.test.annotations.AppModeFull;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+import com.android.cts.mockime.ImeCommand;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+import java.util.regex.Pattern;
+
+public abstract class DatasetFilteringTest extends AbstractLoginActivityTestCase {
+
+    protected DatasetFilteringTest() {
+    }
+
+    protected DatasetFilteringTest(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
+    @Override
+    protected TestRule getMainTestRule() {
+        return RuleChain.outerRule(new MaxVisibleDatasetsRule(4))
+                        .around(super.getMainTestRule());
+    }
+
+
+    private void changeUsername(CharSequence username) {
+        mActivity.onUsername((v) -> v.setText(username));
+    }
+
+
+    @Test
+    public void testFilter() throws Exception {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "b")
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // Only one dataset start with 'aa'
+        changeUsername("aa");
+        mUiBot.assertDatasets(aa);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        changeUsername("aaa");
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+
+        // Delete some text to bring back 2 datasets
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown again
+        changeUsername("");
+        mUiBot.assertDatasets(aa, ab, b);
+    }
+
+    @Test
+    public void testFilter_injectingEvents() throws Exception {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "b")
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        sendKeyEvent("KEYCODE_A");
+        mUiBot.assertDatasets(aa, ab);
+
+        // Only one dataset start with 'aa'
+        sendKeyEvent("KEYCODE_A");
+        mUiBot.assertDatasets(aa);
+
+        // Only two datasets start with 'a'
+        sendKeyEvent("KEYCODE_DEL");
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown
+        sendKeyEvent("KEYCODE_DEL");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        sendKeyEvent("KEYCODE_A");
+        sendKeyEvent("KEYCODE_A");
+        sendKeyEvent("KEYCODE_A");
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testFilter_usingKeyboard() throws Exception {
+        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+        assumeTrue("MockIME not available", mockImeSession != null);
+
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "b")
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        final ImeEventStream stream = mockImeSession.openEventStream();
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        // Wait until the MockIme gets bound to the TestActivity.
+        expectBindInput(stream, Process.myPid(), MOCK_IME_TIMEOUT_MS);
+        expectEvent(stream, editorMatcher("onStartInput", mActivity.getUsername().getId()),
+                MOCK_IME_TIMEOUT_MS);
+
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        final ImeCommand cmd1 = mockImeSession.callCommitText("a", 1);
+        expectCommand(stream, cmd1, MOCK_IME_TIMEOUT_MS);
+        mUiBot.assertDatasets(aa, ab);
+
+        // Only one dataset start with 'aa'
+        final ImeCommand cmd2 = mockImeSession.callCommitText("a", 1);
+        expectCommand(stream, cmd2, MOCK_IME_TIMEOUT_MS);
+        mUiBot.assertDatasets(aa);
+
+        // Only two datasets start with 'a'
+        final ImeCommand cmd3 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+        expectCommand(stream, cmd3, MOCK_IME_TIMEOUT_MS);
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown
+        final ImeCommand cmd4 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+        expectCommand(stream, cmd4, MOCK_IME_TIMEOUT_MS);
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final ImeCommand cmd5 = mockImeSession.callCommitText("aaa", 1);
+        expectCommand(stream, cmd5, MOCK_IME_TIMEOUT_MS);
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_nullValuesAlwaysMatched() throws Exception {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, (String) null)
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Two datasets start with 'a' and one with null value always shown
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // One dataset start with 'aa' and one with null value always shown
+        changeUsername("aa");
+        mUiBot.assertDatasets(aa, b);
+
+        // Two datasets start with 'a' and one with null value always shown
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // With no filter text all datasets should be shown
+        changeUsername("");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa' and one with null value always shown
+        changeUsername("aaa");
+        mUiBot.assertDatasets(b);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_differentPrefixes() throws Exception {
+        final String a = "aaa";
+        final String b = "bra";
+        final String c = "cadabra";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, a)
+                        .setPresentation(a, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, b)
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, c)
+                        .setPresentation(c, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(a, b, c);
+
+        changeUsername("a");
+        mUiBot.assertDatasets(a);
+
+        changeUsername("b");
+        mUiBot.assertDatasets(b);
+
+        changeUsername("c");
+        if (!isInlineMode()) { // With inline, we don't show the datasets now to protect privacy.
+            mUiBot.assertDatasets(c);
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_usingRegex() throws Exception {
+        // Dataset presentations.
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "whatever", Pattern.compile("a|aa"))
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "whatsoever",
+                                Pattern.compile("a|ab"))
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, (String) null, Pattern.compile("b"))
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // Only one dataset start with 'aa'
+        changeUsername("aa");
+        mUiBot.assertDatasets(aa);
+
+        // Only two datasets start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown
+        changeUsername("");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        changeUsername("aaa");
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_disabledUsingNullRegex() throws Exception {
+        // Dataset presentations.
+        final String unfilterable = "Unfilterabled";
+        final String aOrW = "A or W";
+        final String w = "Wazzup";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                // This dataset has a value but filter is disabled
+                .addDataset(new CannedDataset.Builder()
+                        .setUnfilterableField(ID_USERNAME, "a am I")
+                        .setPresentation(unfilterable, isInlineMode())
+                        .build())
+                // This dataset uses pattern to filter
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "whatsoever",
+                                Pattern.compile("a|aw"))
+                        .setPresentation(aOrW, isInlineMode())
+                        .build())
+                // This dataset uses value to filter
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "wazzup")
+                        .setPresentation(w, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(unfilterable, aOrW, w);
+
+        // Only one dataset start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(aOrW);
+
+        // No dataset starts with 'aa'
+        changeUsername("aa");
+        mUiBot.assertNoDatasets();
+
+        // Only one datasets start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(aOrW);
+
+        // With no filter text all datasets should be shown
+        changeUsername("");
+        mUiBot.assertDatasets(unfilterable, aOrW, w);
+
+        // Only one datasets start with 'w'
+        changeUsername("w");
+        if (!isInlineMode()) { // With inline, we don't show the datasets now to protect privacy.
+            mUiBot.assertDatasets(w);
+        }
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        changeUsername("aaa");
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_mixPlainAndRegex() throws Exception {
+        final String plain = "Plain";
+        final String regexPlain = "RegexPlain";
+        final String authRegex = "AuthRegex";
+        final String kitchnSync = "KitchenSync";
+        final Pattern everything = Pattern.compile(".*");
+
+        enableService();
+
+        // Set expectations.
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .build());
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aword")
+                        .setPresentation(plain, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "a ignore", everything)
+                        .setPresentation(regexPlain, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab ignore", everything)
+                        .setAuthentication(authentication)
+                        .setPresentation(authRegex, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab ignore",
+                                everything)
+                        .setPresentation(kitchnSync, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
+
+        // All datasets start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
+
+        // Only the regex datasets should start with 'ab'
+        changeUsername("ab");
+        mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter_usingKeyboard() is enough")
+    public void testFilter_mixPlainAndRegex_usingKeyboard() throws Exception {
+        final String plain = "Plain";
+        final String regexPlain = "RegexPlain";
+        final String authRegex = "AuthRegex";
+        final String kitchnSync = "KitchenSync";
+        final Pattern everything = Pattern.compile(".*");
+
+        enableService();
+
+        // Set expectations.
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .build());
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aword")
+                        .setPresentation(plain, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "a ignore", everything)
+                        .setPresentation(regexPlain, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab ignore", everything)
+                        .setAuthentication(authentication)
+                        .setPresentation(authRegex, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab ignore",
+                                everything)
+                        .setPresentation(kitchnSync, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
+
+        // All datasets start with 'a'
+        sendKeyEvent("KEYCODE_A");
+        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
+
+        // Only the regex datasets should start with 'ab'
+        sendKeyEvent("KEYCODE_B");
+        mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_resetFilter_chooseFirst() throws Exception {
+        resetFilterTest(1);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_resetFilter_chooseSecond() throws Exception {
+        resetFilterTest(2);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_resetFilter_chooseThird() throws Exception {
+        resetFilterTest(3);
+    }
+
+    // Tests that datasets are re-shown and filtering still works after clearing a selected value.
+    private void resetFilterTest(int number) throws Exception {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "b")
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        final String chosenOne;
+        switch (number) {
+            case 1:
+                chosenOne = aa;
+                mActivity.expectAutoFill("aa");
+                break;
+            case 2:
+                chosenOne = ab;
+                mActivity.expectAutoFill("ab");
+                break;
+            case 3:
+                chosenOne = b;
+                mActivity.expectAutoFill("b");
+                break;
+            default:
+                throw new IllegalArgumentException("invalid dataset number: " + number);
+        }
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText username = mActivity.getUsername();
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        callback.assertUiShownEvent(username);
+
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // select the choice
+        mUiBot.selectDataset(chosenOne);
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Change the filled text and check that filtering still works.
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // Reset back to all choices
+        changeUsername("");
+        mUiBot.assertDatasets(aa, ab, b);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/DatePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/DatePickerTestCase.java
new file mode 100644
index 0000000..754f670
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/DatePickerTestCase.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.activities.AbstractDatePickerActivity.ID_DATE_PICKER;
+import static android.autofillservice.cts.activities.AbstractDatePickerActivity.ID_OUTPUT;
+import static android.autofillservice.cts.testcore.Helper.assertDateValue;
+import static android.autofillservice.cts.testcore.Helper.assertNumberOfChildren;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.activities.AbstractDatePickerActivity;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.icu.util.Calendar;
+
+import org.junit.Test;
+
+/**
+ * Base class for {@link AbstractDatePickerActivity} tests.
+ */
+public abstract class DatePickerTestCase<A extends AbstractDatePickerActivity>
+        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    protected A mActivity;
+
+    @Test
+    public void testAutoFillAndSave() throws Exception {
+        assertWithMessage("subclass did not set mActivity").that(mActivity).isNotNull();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.YEAR, 2012);
+        cal.set(Calendar.MONTH, Calendar.DECEMBER);
+        cal.set(Calendar.DAY_OF_MONTH, 20);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                    .setPresentation(createPresentation("The end of the world"))
+                    .setField(ID_OUTPUT, "Y U NO CHANGE ME?")
+                    .setField(ID_DATE_PICKER, cal.getTimeInMillis())
+                    .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER)
+                .build());
+        mActivity.expectAutoFill("2012/11/20", 2012, Calendar.DECEMBER, 20);
+
+        // Trigger auto-fill.
+        mActivity.onOutput((v) -> v.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert properties of DatePicker field.
+        assertTextIsSanitized(fillRequest.structure, ID_DATE_PICKER);
+        assertNumberOfChildren(fillRequest.structure, ID_DATE_PICKER, 0);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The end of the world");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Trigger save.
+        mActivity.setDate(2010, Calendar.DECEMBER, 12);
+        mActivity.tapOk();
+
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
+
+        // Assert sanitization on save: everything should be available!
+        assertDateValue(findNodeByResourceId(saveRequest.structure, ID_DATE_PICKER), 2010, 11, 12);
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_OUTPUT), "2010/11/12");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
new file mode 100644
index 0000000..5bd6b94
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_CC_NUMBER;
+import static android.autofillservice.cts.activities.LoginActivity.BACKDOOR_USERNAME;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.NULL_DATASET_ID;
+import static android.autofillservice.cts.testcore.Helper.assertDeprecatedClientState;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForAuthenticationSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetAuthenticationSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetShown;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForSaveShown;
+import static android.autofillservice.cts.testcore.Helper.assertNoDeprecatedClientState;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilDisconnected;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.activities.CheckoutActivity;
+import android.autofillservice.cts.inline.InlineFillEventHistoryTest;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.FillResponse;
+import android.view.View;
+
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This is the common test cases with {@link FillEventHistoryTest} and
+ * {@link InlineFillEventHistoryTest}.
+ */
+@AppModeFull(reason = "Service-specific test")
+public abstract class FillEventHistoryCommonTestCase extends AbstractLoginActivityTestCase {
+
+    protected FillEventHistoryCommonTestCase() {}
+
+    protected FillEventHistoryCommonTestCase(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
+    protected Bundle getBundle(String key, String value) {
+        final Bundle bundle = new Bundle();
+        bundle.putString(key, value);
+        return bundle;
+    }
+
+    @Test
+    public void testDatasetAuthenticationSelected() throws Exception {
+        enableService();
+
+        // Set up FillResponse with dataset authentication
+        Bundle clientState = new Bundle();
+        clientState.putCharSequence("clientStateKey", "clientStateValue");
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation("Dataset", isInlineMode())
+                        .build());
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setId("name")
+                        .setPresentation("authentication", isInlineMode())
+                        .setAuthentication(authentication)
+                        .build())
+                .setExtras(clientState).build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        // Authenticate
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("authentication");
+        mActivity.assertAutoFilled();
+
+        // Verify fill selection
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+        assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
+        assertFillEventForDatasetAuthenticationSelected(events.get(1), "name",
+                "clientStateKey", "clientStateValue");
+    }
+
+    @Test
+    public void testAuthenticationSelected() throws Exception {
+        enableService();
+
+        // Set up FillResponse with response wide authentication
+        Bundle clientState = new Bundle();
+        clientState.putCharSequence("clientStateKey", "clientStateValue");
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "username")
+                                .setId("name")
+                                .setPresentation("dataset", isInlineMode())
+                                .build())
+                        .setExtras(clientState).build());
+
+        sReplier.addResponse(new CannedFillResponse.Builder().setExtras(clientState)
+                .setPresentation("authentication", isInlineMode())
+                .setAuthentication(authentication, ID_USERNAME)
+                .build());
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        // Authenticate
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("authentication");
+        mUiBot.waitForIdle();
+        mUiBot.selectDataset("dataset");
+        mUiBot.waitForIdle();
+
+        // Verify fill selection
+        final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(4);
+        assertDeprecatedClientState(selection, "clientStateKey", "clientStateValue");
+        List<Event> events = selection.getEvents();
+        assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
+        assertFillEventForAuthenticationSelected(events.get(1), NULL_DATASET_ID,
+                "clientStateKey", "clientStateValue");
+        assertFillEventForDatasetShown(events.get(2), "clientStateKey", "clientStateValue");
+        assertFillEventForDatasetSelected(events.get(3), "name",
+                "clientStateKey", "clientStateValue");
+    }
+
+    @Test
+    public void testDatasetSelected_twoResponses() throws Exception {
+        enableService();
+
+        // Set up first partition with an anonymous dataset
+        Bundle clientState1 = new Bundle();
+        clientState1.putCharSequence("clientStateKey", "Value1");
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation("dataset1", isInlineMode())
+                        .build())
+                .setExtras(clientState1)
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mUiBot.waitForIdle();
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            assertDeprecatedClientState(selection, "clientStateKey", "Value1");
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value1");
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID,
+                    "clientStateKey", "Value1");
+        }
+
+        // Set up second partition with a named dataset
+        Bundle clientState2 = new Bundle();
+        clientState2.putCharSequence("clientStateKey", "Value2");
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_PASSWORD, "password2")
+                                .setPresentation("dataset2", isInlineMode())
+                                .setId("name2")
+                                .build())
+                .addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_PASSWORD, "password3")
+                                .setPresentation("dataset3", isInlineMode())
+                                .setId("name3")
+                                .build())
+                .setExtras(clientState2)
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_PASSWORD).build());
+        mActivity.expectPasswordAutoFill("password3");
+
+        // Trigger autofill on password
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset3");
+        mUiBot.waitForIdle();
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            assertDeprecatedClientState(selection, "clientStateKey", "Value2");
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
+            assertFillEventForDatasetSelected(events.get(1), "name3",
+                    "clientStateKey", "Value2");
+        }
+
+        mActivity.onPassword((v) -> v.setText("new password"));
+        mActivity.syncRunOnUiThread(() -> mActivity.finish());
+        waitUntilDisconnected();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(4);
+            assertDeprecatedClientState(selection, "clientStateKey", "Value2");
+
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
+            assertFillEventForDatasetSelected(events.get(1), "name3",
+                    "clientStateKey", "Value2");
+            assertFillEventForDatasetShown(events.get(2), "clientStateKey", "Value2");
+            assertFillEventForSaveShown(events.get(3), NULL_DATASET_ID,
+                    "clientStateKey", "Value2");
+        }
+    }
+
+    @Test
+    public void testNoEvents_whenServiceReturnsNullResponse() throws Exception {
+        enableService();
+
+        // First reset
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation("dataset1", isInlineMode())
+                        .build())
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mUiBot.waitForIdleSync();
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            assertNoDeprecatedClientState(selection);
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+        }
+
+        // Second request
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.waitForIdleSync();
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasets();
+        waitUntilDisconnected();
+
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    @Test
+    public void testNoEvents_whenServiceReturnsFailure() throws Exception {
+        enableService();
+
+        // First reset
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation("dataset1", isInlineMode())
+                        .build())
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mUiBot.waitForIdleSync();
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            assertNoDeprecatedClientState(selection);
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+        }
+
+        // Second request
+        sReplier.addResponse(new CannedFillResponse.Builder().returnFailure("D'OH!").build());
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.waitForIdleSync();
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasets();
+        waitUntilDisconnected();
+
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    @Test
+    public void testNoEvents_whenServiceTimesout() throws Exception {
+        enableService();
+
+        // First reset
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation("dataset1", isInlineMode())
+                        .build())
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            assertNoDeprecatedClientState(selection);
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+        }
+
+        // Second request
+        sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+        waitUntilDisconnected();
+
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    /**
+     * Tests the following scenario:
+     *
+     * <ol>
+     *    <li>Activity A is launched.
+     *    <li>Activity A triggers autofill.
+     *    <li>Activity B is launched.
+     *    <li>Activity B triggers autofill.
+     *    <li>User goes back to Activity A.
+     *    <li>Activity A triggers autofill.
+     *    <li>User triggers save on Activity A - at this point, service should have stats of
+     *        activity A.
+     * </ol>
+     */
+    @Test
+    public void testEventsFromPreviousSessionIsDiscarded() throws Exception {
+        enableService();
+
+        // Launch activity A
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setExtras(getBundle("activity", "A"))
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill and IME on activity A.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+
+        // Verify fill selection for Activity A
+        final FillEventHistory selectionA = InstrumentedAutoFillService.getFillEventHistory(0);
+        assertDeprecatedClientState(selectionA, "activity", "A");
+
+        // Launch activity B
+        mActivity.startActivity(new Intent(mActivity, CheckoutActivity.class));
+        mUiBot.assertShownByRelativeId(ID_CC_NUMBER);
+
+        // Trigger autofill on activity B
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setExtras(getBundle("activity", "B"))
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_CC_NUMBER, "4815162342")
+                        .setPresentation("datasetB", isInlineMode())
+                        .build())
+                .build());
+        mUiBot.focusByRelativeId(ID_CC_NUMBER);
+        sReplier.getNextFillRequest();
+
+        // Verify fill selection for Activity B
+        final FillEventHistory selectionB = InstrumentedAutoFillService.getFillEventHistory(1);
+        assertDeprecatedClientState(selectionB, "activity", "B");
+        assertFillEventForDatasetShown(selectionB.getEvents().get(0), "activity", "B");
+
+        // Set response for back to activity A
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setExtras(getBundle("activity", "A"))
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Now switch back to A...
+        mUiBot.pressBack(); // dismiss autofill
+        mUiBot.pressBack(); // dismiss keyboard (or task, if there was no keyboard)
+        final AtomicBoolean focusOnA = new AtomicBoolean();
+        mActivity.syncRunOnUiThread(() -> focusOnA.set(mActivity.hasWindowFocus()));
+        if (!focusOnA.get()) {
+            mUiBot.pressBack(); // dismiss task, if the last pressBack dismissed only the keyboard
+        }
+        mUiBot.assertShownByRelativeId(ID_USERNAME);
+        assertWithMessage("root window has no focus")
+                .that(mActivity.getWindow().getDecorView().hasWindowFocus()).isTrue();
+
+        sReplier.getNextFillRequest();
+
+        // ...and trigger save
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        sReplier.getNextSaveRequest();
+
+        // Finally, make sure history is right
+        final FillEventHistory finalSelection = InstrumentedAutoFillService.getFillEventHistory(1);
+        assertDeprecatedClientState(finalSelection, "activity", "A");
+        assertFillEventForSaveShown(finalSelection.getEvents().get(0), NULL_DATASET_ID, "activity",
+                "A");
+    }
+
+    @Test
+    public void testContextCommitted_withoutFlagOnLastResponse() throws Exception {
+        enableService();
+        // Trigger 1st autofill request
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setPresentation("dataset1", isInlineMode())
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill(BACKDOOR_USERNAME);
+        // Trigger autofill and IME on username.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+        // Verify fill history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+        }
+
+        // Trigger 2nd autofill request (which will clear the fill event history)
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation("dataset2", isInlineMode())
+                        .build())
+                // don't set flags
+                .build());
+        mActivity.expectPasswordAutoFill("whatever");
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset2");
+        mActivity.assertAutoFilled();
+        // Verify fill history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+        }
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/LoginActivityCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/LoginActivityCommonTestCase.java
new file mode 100644
index 0000000..2b5ddd4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/LoginActivityCommonTestCase.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_MOAR_RESPONSES;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilDisconnected;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.inline.InlineLoginActivityTest;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.UiBot;
+import android.service.autofill.FillContext;
+import android.view.View;
+
+import org.junit.Test;
+
+/**
+ * This is the common test cases with {@link LoginActivityTest} and {@link InlineLoginActivityTest}.
+ */
+public abstract class LoginActivityCommonTestCase extends AbstractLoginActivityTestCase {
+
+    protected LoginActivityCommonTestCase() {}
+
+    protected LoginActivityCommonTestCase(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
+    @Test
+    public void testAutoFillNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+
+        // Make sure a fill request is called but don't check for connected() - as we're returning
+        // a null response, the service might have been disconnected already by the time we assert
+        // it.
+        sReplier.getNextFillRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Test connection lifecycle.
+        waitUntilDisconnected();
+    }
+
+    @Test
+    public void testAutoFillNoDatasets_multipleFields_alwaysNull() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE)
+                .addResponse(NO_MOAR_RESPONSES);
+
+        // Trigger autofill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Tap back and forth to make sure no more requests are shown
+
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.assertNoDatasetsEver();
+
+        mActivity.onUsername(View::requestFocus);
+        mUiBot.assertNoDatasetsEver();
+
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.assertNoDatasetsEver();
+    }
+
+
+    @Test
+    public void testAutofill_oneDataset() throws Exception {
+        testBasicLoginAutofill(/* numDatasets= */ 1, /* selectedDatasetIndex= */ 0);
+    }
+
+    @Test
+    public void testAutofill_twoDatasets_selectFirstDataset() throws Exception {
+        testBasicLoginAutofill(/* numDatasets= */ 2, /* selectedDatasetIndex= */ 0);
+
+    }
+
+    @Test
+    public void testAutofill_twoDatasets_selectSecondDataset() throws Exception {
+        testBasicLoginAutofill(/* numDatasets= */ 2, /* selectedDatasetIndex= */ 1);
+    }
+
+    private void testBasicLoginAutofill(int numDatasets, int selectedDatasetIndex)
+            throws Exception {
+        // Set service.
+        enableService();
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final View username = mActivity.getUsername();
+        final View password = mActivity.getPassword();
+
+        String[] expectedDatasets = new String[numDatasets];
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder();
+        for (int i = 0; i < numDatasets; i++) {
+            builder.addDataset(new CannedFillResponse.CannedDataset.Builder()
+                    .setField(ID_USERNAME, "dude" + i)
+                    .setField(ID_PASSWORD, "sweet" + i)
+                    .setPresentation("The Dude" + i, isInlineMode())
+                    .build());
+            expectedDatasets[i] = "The Dude" + i;
+        }
+
+        sReplier.addResponse(builder.build());
+        mActivity.expectAutoFill("dude" + selectedDatasetIndex, "sweet" + selectedDatasetIndex);
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        mUiBot.assertDatasets(expectedDatasets);
+        callback.assertUiShownEvent(username);
+
+        mUiBot.selectDataset(expectedDatasets[selectedDatasetIndex]);
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        callback.assertUiHiddenEvent(username);
+
+        // Make sure input was sanitized.
+        final InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
+        assertWithMessage("CancelationSignal is null").that(request.cancellationSignal).isNotNull();
+        assertTextIsSanitized(request.structure, ID_PASSWORD);
+        final FillContext fillContext = request.contexts.get(request.contexts.size() - 1);
+        assertThat(fillContext.getFocusedId())
+                .isEqualTo(findAutofillIdByResourceId(fillContext, ID_USERNAME));
+        if (isInlineMode()) {
+            assertThat(request.inlineRequest).isNotNull();
+        } else {
+            assertThat(request.inlineRequest).isNull();
+        }
+
+        // Make sure initial focus was properly set.
+        assertWithMessage("Username node is not focused").that(
+                findNodeByResourceId(request.structure, ID_USERNAME).isFocused()).isTrue();
+        assertWithMessage("Password node is focused").that(
+                findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
+    }
+
+    @Test
+    public void testClearFocusBeforeRespond() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Clear focus before responded
+        mActivity.onUsername(View::clearFocus);
+        mUiBot.waitForIdleSync();
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build());
+        sReplier.addResponse(builder.build());
+        sReplier.getNextFillRequest();
+
+        // Confirm no datasets shown
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    public void testSwitchFocusBeforeResponse() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Trigger second fill request
+        mUiBot.selectByRelativeId(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        // Respond for username
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+
+        // Set expectations and respond for password
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation("The Password", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        // confirm second response shown
+        mUiBot.assertDatasets("The Password");
+    }
+
+    @Test
+    public void testManualRequestWhileFirstResponseDelayed() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Trigger second fill request
+        mActivity.forceAutofillOnUsername();
+        mUiBot.waitForIdleSync();
+
+        // Respond for first request
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        // Set expectations and respond for second request
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude2")
+                        .setPresentation("The Dude 2", isInlineMode())
+                        .build()).build());
+        sReplier.getNextFillRequest();
+
+        // confirm second response shown
+        mUiBot.assertDatasets("The Dude 2");
+    }
+
+    @Test
+    public void testResponseFirstAfterResponseSecond() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Trigger second fill request
+        mActivity.forceAutofillOnUsername();
+        mUiBot.waitForIdleSync();
+
+        // Respond for first request
+        sReplier.addResponse(new CannedFillResponse.Builder(CannedFillResponse.ResponseType.DELAY)
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        // Set expectations and respond for second request
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude2")
+                        .setPresentation("The Dude 2", isInlineMode())
+                        .build()).build());
+        sReplier.getNextFillRequest();
+
+        // confirm second response shown
+        mUiBot.assertDatasets("The Dude 2");
+
+        // Wait first response was sent
+        sReplier.getNextFillRequest();
+
+        // confirm second response still shown
+        mUiBot.assertDatasets("The Dude 2");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/TimePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/TimePickerTestCase.java
new file mode 100644
index 0000000..77af4dc
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/TimePickerTestCase.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.activities.AbstractTimePickerActivity.ID_OUTPUT;
+import static android.autofillservice.cts.activities.AbstractTimePickerActivity.ID_TIME_PICKER;
+import static android.autofillservice.cts.testcore.Helper.assertNumberOfChildren;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertTimeValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.activities.AbstractTimePickerActivity;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.icu.util.Calendar;
+
+import org.junit.Test;
+
+/**
+ * Base class for {@link AbstractTimePickerActivity} tests.
+ */
+public abstract class TimePickerTestCase<A extends AbstractTimePickerActivity>
+        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    protected A mActivity;
+
+    @Test
+    public void testAutoFillAndSave() throws Exception {
+        assertWithMessage("subclass did not set mActivity").that(mActivity).isNotNull();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 4);
+        cal.set(Calendar.MINUTE, 20);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                    .setPresentation(createPresentation("Adventure Time"))
+                    .setField(ID_OUTPUT, "Y U NO CHANGE ME?")
+                    .setField(ID_TIME_PICKER, cal.getTimeInMillis())
+                    .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_TIME_PICKER)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onOutput((v) -> v.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert properties of TimePicker field.
+        assertTextIsSanitized(fillRequest.structure, ID_TIME_PICKER);
+        assertNumberOfChildren(fillRequest.structure, ID_TIME_PICKER, 0);
+        // Auto-fill it.
+        mActivity.expectAutoFill("4:20", 4, 20);
+        mUiBot.selectDataset("Adventure Time");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Trigger save.
+        mActivity.setTime(10, 40);
+        mActivity.tapOk();
+
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
+
+        // Assert sanitization on save: everything should be available!
+        assertTimeValue(findNodeByResourceId(saveRequest.structure, ID_TIME_PICKER), 10, 40);
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_OUTPUT), "10:40");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java
new file mode 100644
index 0000000..c988305
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java
@@ -0,0 +1,1197 @@
+/*
+ * 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.autofillservice.cts.dropdown;
+
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.UNUSED_AUTOFILL_VALUE;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.regex.Pattern;
+
+public class AuthenticationTest extends AbstractLoginActivityTestCase {
+
+    @Test
+    public void testDatasetAuthTwoFields() throws Exception {
+        datasetAuthTwoFields(false);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthTwoFieldsUserCancelsFirstAttempt() throws Exception {
+        datasetAuthTwoFields(true);
+    }
+
+    private void datasetAuthTwoFields(boolean cancelFirstAttempt) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // Make sure UI is show on 2nd field as well
+        final View password = mActivity.getPassword();
+        requestFocusOnPassword();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // Now tap on 1st field to show it again...
+        requestFocusOnUsername();
+        callback.assertUiHiddenEvent(password);
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        if (cancelFirstAttempt) {
+            // Trigger the auth dialog, but emulate cancel.
+            AuthenticationActivity.setResultCode(RESULT_CANCELED);
+            mUiBot.selectDataset("Tap to auth dataset");
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(username);
+            mUiBot.assertDatasets("Tap to auth dataset");
+
+            // Make sure it's still shown on other fields...
+            requestFocusOnPassword();
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(password);
+            mUiBot.assertDatasets("Tap to auth dataset");
+
+            // Tap on 1st field to show it again...
+            requestFocusOnUsername();
+            callback.assertUiHiddenEvent(password);
+            callback.assertUiShownEvent(username);
+        }
+
+        // ...and select it this time
+        AuthenticationActivity.setResultCode(RESULT_OK);
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthTwoFieldsReplaceResponse() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .build());
+
+        // Set up the authentication response client state
+        final Bundle authentionClientState = new Bundle();
+        authentionClientState.putCharSequence("clientStateKey1", "clientStateValue1");
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, (AutofillValue) null)
+                        .setField(ID_PASSWORD, (AutofillValue) null)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .setExtras(authentionClientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+
+        // Select a dataset from the new response
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset("Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("clientStateKey1");
+        assertThat(extraValue).isEqualTo("clientStateValue1");
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthTwoFieldsNoValues() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intent
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, (String) null)
+                        .setField(ID_PASSWORD, (String) null)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthTwoDatasets() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication1 = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+        final IntentSender authentication2 = AuthenticationActivity.createSender(mContext, 2,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset 1"))
+                        .setAuthentication(authentication1)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset 2"))
+                        .setAuthentication(authentication2)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset 1", "Tap to auth dataset 2");
+
+        mUiBot.selectDataset("Tap to auth dataset 1");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthMixedSelectAuth() throws Exception {
+        datasetAuthMixedTest(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthMixedSelectNonAuth() throws Exception {
+        datasetAuthMixedTest(false);
+    }
+
+    private void datasetAuthMixedTest(boolean selectAuth) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .setPresentation(createPresentation("What, me auth?"))
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        if (selectAuth) {
+            mActivity.expectAutoFill("dude", "sweet");
+        } else {
+            mActivity.expectAutoFill("DUDE", "SWEET");
+        }
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+
+        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
+        mUiBot.selectDataset(chosenOne);
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
+    public void testDatasetAuthNoFiltering() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // ..then type something to hide it.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert it's shown again...
+        mActivity.onUsername((v) -> v.setText(""));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // ...and select it this time
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
+    public void testDatasetAuthFilteringUsingAutofillValue() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("DS1"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE,THE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .setPresentation(createPresentation("DS2"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ZzBottom")
+                        .setField(ID_PASSWORD, "top")
+                        .setPresentation(createPresentation("DS3"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("DS1", "DS2", "DS3");
+
+        // ...then type something to hide them.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert they're shown again...
+        mActivity.onUsername((v) -> v.setText(""));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("DS1", "DS2", "DS3");
+
+        // ...then filter for 2
+        mActivity.onUsername((v) -> v.setText("d"));
+        mUiBot.assertDatasets("DS1", "DS2");
+
+        // ...up to 1
+        mActivity.onUsername((v) -> v.setText("du"));
+        mUiBot.assertDatasets("DS1", "DS2");
+        mActivity.onUsername((v) -> v.setText("dud"));
+        mUiBot.assertDatasets("DS1", "DS2");
+        mActivity.onUsername((v) -> v.setText("dude"));
+        mUiBot.assertDatasets("DS1", "DS2");
+        mActivity.onUsername((v) -> v.setText("dude,"));
+        mUiBot.assertDatasets("DS2");
+
+        // Now delete the char and assert 2 are shown again...
+        mActivity.onUsername((v) -> v.setText("dude"));
+        final UiObject2 picker = mUiBot.assertDatasets("DS1", "DS2");
+
+        // ...and select it this time
+        mUiBot.selectDataset(picker, "DS1");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetAuthFilteringUsingRegex() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+
+        final Pattern min2Chars = Pattern.compile(".{2,}");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE, min2Chars)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // ...then type something to hide it.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // ...now type something again to show it, as the input will have 2 chars.
+        mActivity.onUsername((v) -> v.setText("aa"));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // Delete the char and assert it's not shown again...
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // ...then type something again to show it, as the input will have 2 chars.
+        mActivity.onUsername((v) -> v.setText("aa"));
+        callback.assertUiShownEvent(username);
+
+        // ...and select it this time
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
+    public void testDatasetAuthMixedFilteringSelectAuth() throws Exception {
+        datasetAuthMixedFilteringTest(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
+    public void testDatasetAuthMixedFilteringSelectNonAuth() throws Exception {
+        datasetAuthMixedFilteringTest(false);
+    }
+
+    private void datasetAuthMixedFilteringTest(boolean selectAuth) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "DUDE")
+                .setField(ID_PASSWORD, "SWEET")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("What, me auth?"))
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        if (selectAuth) {
+            mActivity.expectAutoFill("DUDE", "SWEET");
+        } else {
+            mActivity.expectAutoFill("dude", "sweet");
+        }
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+
+        // Filter the auth dataset.
+        mActivity.onUsername((v) -> v.setText("d"));
+        mUiBot.assertDatasets("What, me auth?");
+
+        // Filter all.
+        mActivity.onUsername((v) -> v.setText("dw"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert the non-auth is shown again.
+        mActivity.onUsername((v) -> v.setText("d"));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("What, me auth?");
+
+        // Delete again and assert all dataset are shown.
+        mActivity.onUsername((v) -> v.setText(""));
+        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+
+        // ...and select it this time
+        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
+        mUiBot.selectDataset(chosenOne);
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetAuthClientStateSetOnIntentOnly() throws Exception {
+        fillDatasetAuthWithClientState(ClientStateLocation.INTENT_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthClientStateSetOnIntentOnly() is enough")
+    public void testDatasetAuthClientStateSetOnFillResponseOnly() throws Exception {
+        fillDatasetAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthClientStateSetOnIntentOnly() is enough")
+    public void testDatasetAuthClientStateSetOnIntentAndFillResponse() throws Exception {
+        fillDatasetAuthWithClientState(ClientStateLocation.BOTH);
+    }
+
+    private void fillDatasetAuthWithClientState(ClientStateLocation where) throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare the authenticated response
+        final CannedDataset dataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? AuthenticationActivity.createSender(mContext, 1,
+                        dataset)
+                : AuthenticationActivity.createSender(mContext, 1,
+                        dataset, Helper.newClientState("CSI", "FromIntent"));
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setExtras(Helper.newClientState("CSI", "FromResponse"))
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Tap authentication request.
+        mUiBot.selectDataset("Tap to auth dataset");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Now trigger save.
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert client state on authentication activity.
+        Helper.assertAuthenticationClientState("auth activity", AuthenticationActivity.getData(),
+                "CSI", "FromResponse");
+
+        // Assert client state on save request.
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? "FromResponse" : "FromIntent";
+        Helper.assertAuthenticationClientState("on save", saveRequest.data, "CSI", expectedValue);
+    }
+
+    @Test
+    public void testFillResponseAuthBothFields() throws Exception {
+        fillResponseAuthBothFields(false);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+    public void testFillResponseAuthBothFieldsUserCancelsFirstAttempt() throws Exception {
+        fillResponseAuthBothFields(true);
+    }
+
+    private void fillResponseAuthBothFields(boolean cancelFirstAttempt) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final Bundle clientState = new Bundle();
+        clientState.putString("numbers", "4815162342");
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setId("name")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .setExtras(clientState).build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(clientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Make sure UI is show on 2nd field as well
+        final View password = mActivity.getPassword();
+        requestFocusOnPassword();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Now tap on 1st field to show it again...
+        requestFocusOnUsername();
+        callback.assertUiHiddenEvent(password);
+        callback.assertUiShownEvent(username);
+
+        if (cancelFirstAttempt) {
+            // Trigger the auth dialog, but emulate cancel.
+            AuthenticationActivity.setResultCode(RESULT_CANCELED);
+            mUiBot.selectDataset("Tap to auth response");
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(username);
+            mUiBot.assertDatasets("Tap to auth response");
+
+            // Make sure it's still shown on other fields...
+            requestFocusOnPassword();
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(password);
+            mUiBot.assertDatasets("Tap to auth response");
+
+            // Tap on 1st field to show it again...
+            requestFocusOnUsername();
+            callback.assertUiHiddenEvent(password);
+            callback.assertUiShownEvent(username);
+        }
+
+        // ...and select it this time
+        AuthenticationActivity.setResultCode(RESULT_OK);
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(username);
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+        mUiBot.selectDataset(picker, "Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("numbers");
+        assertThat(extraValue).isEqualTo("4815162342");
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+    public void testFillResponseAuthJustOneField() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final Bundle clientState = new Bundle();
+        clientState.putString("numbers", "4815162342");
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME)
+                .setIgnoreFields(ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(clientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Make sure UI is not show on 2nd field
+        requestFocusOnPassword();
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+        // Now tap on 1st field to show it again...
+        requestFocusOnUsername();
+        callback.assertUiShownEvent(username);
+
+        // ...and select it this time
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset(picker, "Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("numbers");
+        assertThat(extraValue).isEqualTo("4815162342");
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+    public void testFillResponseAuthWhenAppCallsCancel() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setId("name")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .build());
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Disables autofill so it's not triggered again after the auth activity is finished
+        // (and current session is canceled) and the login activity is resumed.
+        username.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO);
+
+        // Autofill it.
+        final CountDownLatch latch = new CountDownLatch(1);
+        AuthenticationActivity.setResultCode(latch, RESULT_OK);
+
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+
+        // Cancel session...
+        mActivity.getAutofillManager().cancel();
+
+        // ...before finishing the Auth UI.
+        latch.countDown();
+
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+    public void testFillResponseAuthServiceHasNoDataButCanSave() throws Exception {
+        fillResponseAuthServiceHasNoDataTest(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+    public void testFillResponseAuthServiceHasNoData() throws Exception {
+        fillResponseAuthServiceHasNoDataTest(false);
+    }
+
+    private void fillResponseAuthServiceHasNoDataTest(boolean canSave) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final CannedFillResponse response = canSave
+                ? new CannedFillResponse.Builder()
+                        .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                        .build()
+                : CannedFillResponse.NO_RESPONSE;
+
+        final IntentSender authentication =
+                AuthenticationActivity.createSender(mContext, 1, response);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .build());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+
+        // Select the authentication dialog.
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        if (!canSave) {
+            // Our work is done!
+            return;
+        }
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        sReplier.assertNoUnhandledSaveRequests();
+        assertThat(saveRequest.datasetIds).isNull();
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode usernameNode = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(usernameNode, "malkovich");
+        final ViewNode passwordNode = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+        assertTextAndValue(passwordNode, "malkovich");
+    }
+
+    @Test
+    public void testFillResponseAuthClientStateSetOnIntentOnly() throws Exception {
+        fillResponseAuthWithClientState(ClientStateLocation.INTENT_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthClientStateSetOnIntentOnly() is enough")
+    public void testFillResponseAuthClientStateSetOnFillResponseOnly() throws Exception {
+        fillResponseAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthClientStateSetOnIntentOnly() is enough")
+    public void testFillResponseAuthClientStateSetOnIntentAndFillResponse() throws Exception {
+        fillResponseAuthWithClientState(ClientStateLocation.BOTH);
+    }
+
+    enum ClientStateLocation {
+        INTENT_ONLY,
+        FILL_RESPONSE_ONLY,
+        BOTH
+    }
+
+    private void fillResponseAuthWithClientState(ClientStateLocation where) throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare the authenticated response
+        final CannedFillResponse.Builder authenticatedResponseBuilder =
+                new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Dataset"))
+                        .build());
+
+        if (where == ClientStateLocation.FILL_RESPONSE_ONLY || where == ClientStateLocation.BOTH) {
+            authenticatedResponseBuilder.setExtras(
+                    Helper.newClientState("CSI", "FromAuthResponse"));
+        }
+
+        final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? AuthenticationActivity.createSender(mContext, 1,
+                authenticatedResponseBuilder.build())
+                : AuthenticationActivity.createSender(mContext, 1,
+                        authenticatedResponseBuilder.build(),
+                        Helper.newClientState("CSI", "FromIntent"));
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME)
+                .setIgnoreFields(ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(Helper.newClientState("CSI", "FromResponse"))
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Tap authentication request.
+        mUiBot.selectDataset("Tap to auth response");
+
+        // Tap dataset.
+        mUiBot.selectDataset("Dataset");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Now trigger save.
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert client state on authentication activity.
+        Helper.assertAuthenticationClientState("auth activity", AuthenticationActivity.getData(),
+                "CSI", "FromResponse");
+
+        // Assert client state on save request.
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? "FromAuthResponse" : "FromIntent";
+        Helper.assertAuthenticationClientState("on save", saveRequest.data, "CSI", expectedValue);
+    }
+
+    @Test
+    public void testFillResponseFiltering() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final Bundle clientState = new Bundle();
+        clientState.putString("numbers", "4815162342");
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setId("name")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .setExtras(clientState).build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(clientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // ..then type something to hide it.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert it's shown again...
+        mActivity.onUsername((v) -> v.setText(""));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // ...and select it this time
+        AuthenticationActivity.setResultCode(RESULT_OK);
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(username);
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+        mUiBot.selectDataset(picker, "Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("numbers");
+        assertThat(extraValue).isEqualTo("4815162342");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/CheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/CheckoutActivityTest.java
new file mode 100644
index 0000000..1e3535e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/CheckoutActivityTest.java
@@ -0,0 +1,801 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_ADDRESS;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_CC_EXPIRATION;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_CC_NUMBER;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_DATE_PICKER;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_HOME_ADDRESS;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_SAVE_CC;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_TIME_PICKER;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_WORK_ADDRESS;
+import static android.autofillservice.cts.activities.CheckoutActivity.INDEX_ADDRESS_WORK;
+import static android.autofillservice.cts.activities.CheckoutActivity.INDEX_CC_EXPIRATION_NEVER;
+import static android.autofillservice.cts.activities.CheckoutActivity.INDEX_CC_EXPIRATION_TODAY;
+import static android.autofillservice.cts.activities.CheckoutActivity.INDEX_CC_EXPIRATION_TOMORROW;
+import static android.autofillservice.cts.testcore.Helper.assertListValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertToggleIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertToggleValue;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.view.View.AUTOFILL_TYPE_LIST;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.CheckoutActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MultipleTimesRadioGroupListener;
+import android.autofillservice.cts.testcore.MultipleTimesTimeListener;
+import android.autofillservice.cts.testcore.OneTimeCompoundButtonListener;
+import android.autofillservice.cts.testcore.OneTimeDateListener;
+import android.autofillservice.cts.testcore.OneTimeSpinnerListener;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.icu.util.Calendar;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.FillContext;
+import android.service.autofill.ImageTransformation;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.RadioGroup;
+import android.widget.RemoteViews;
+import android.widget.Spinner;
+import android.widget.TimePicker;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+/**
+ * Test case for an activity containing non-TextField views.
+ */
+public class CheckoutActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<CheckoutActivity> {
+
+    private CheckoutActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<CheckoutActivity> getActivityRule() {
+        return new AutofillActivityTestRule<CheckoutActivity>(CheckoutActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testAutofill() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setPresentation(createPresentation("ACME CC"))
+                .setField(ID_CC_NUMBER, "4815162342")
+                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
+                .setField(ID_ADDRESS, 1)
+                .setField(ID_SAVE_CC, true)
+                .build());
+        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
+                true);
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert properties of Spinner field.
+        final ViewNode ccExpirationNode =
+                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
+        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
+        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
+        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
+        assertWithMessage("ccExpirationNode.getAutoFillOptions()").that(options).isNotNull();
+        assertWithMessage("Wrong auto-fill options for spinner").that(options).asList()
+                .containsExactly((Object [])
+                        getContext().getResources().getStringArray(R.array.cc_expiration_values))
+                .inOrder();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("ACME CC");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofill() is enough")
+    public void testAutofillDynamicAdapter() throws Exception {
+        // Set activity.
+        mActivity.onCcExpiration((v) -> v.setAdapter(new ArrayAdapter<String>(getContext(),
+                android.R.layout.simple_spinner_item,
+                Arrays.asList("YESTERDAY", "TODAY", "TOMORROW", "NEVER"))));
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setPresentation(createPresentation("ACME CC"))
+                .setField(ID_CC_NUMBER, "4815162342")
+                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
+                .setField(ID_ADDRESS, 1)
+                .setField(ID_SAVE_CC, true)
+                .build());
+        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
+                true);
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert properties of Spinner field.
+        final ViewNode ccExpirationNode =
+                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
+        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
+        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
+        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
+        assertWithMessage("ccExpirationNode.getAutoFillOptions()").that(options).isNull();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("ACME CC");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    // TODO: this should be a pure unit test exercising onProvideAutofillStructure(),
+    // but that would require creating a custom ViewStructure.
+    @Test
+    @AppModeFull(reason = "Unit test")
+    public void testGetAutofillOptionsSorted() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set activity.
+        mActivity.onCcExpirationAdapter((adapter) -> adapter.sort((a, b) -> {
+            return ((String) a).compareTo((String) b);
+        }));
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setPresentation(createPresentation("ACME CC"))
+                .setField(ID_CC_NUMBER, "4815162342")
+                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
+                .setField(ID_ADDRESS, 1)
+                .setField(ID_SAVE_CC, true)
+                .build());
+        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
+                true);
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert properties of Spinner field.
+        final ViewNode ccExpirationNode =
+                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
+        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
+        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
+        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
+        assertWithMessage("Wrong auto-fill options for spinner").that(options).asList()
+                .containsExactly("never", "today", "tomorrow", "yesterday").inOrder();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("ACME CC");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testSanitization() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD,
+                        ID_CC_NUMBER, ID_CC_EXPIRATION, ID_ADDRESS, ID_SAVE_CC)
+                .build());
+
+        // Dynamically change view contents
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
+        mActivity.onHomeAddress((v) -> v.setChecked(true));
+        mActivity.onSaveCc((v) -> v.setChecked(true));
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+
+        // Assert sanitization on fill request: everything should be sanitized!
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        assertTextIsSanitized(fillRequest.structure, ID_CC_NUMBER);
+        assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
+        assertToggleIsSanitized(fillRequest.structure, ID_HOME_ADDRESS);
+        assertToggleIsSanitized(fillRequest.structure, ID_SAVE_CC);
+
+        // Trigger save.
+        mActivity.onCcNumber((v) -> v.setText("4815162342"));
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
+        mActivity.onAddress((v) -> v.check(R.id.work_address));
+        mActivity.onSaveCc((v) -> v.setChecked(false));
+        mActivity.tapBuy();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_CREDIT_CARD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Assert sanitization on save: everything should be available!
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CC_NUMBER), "4815162342");
+        assertListValue(findNodeByResourceId(saveRequest.structure, ID_CC_EXPIRATION),
+                INDEX_CC_EXPIRATION_TODAY);
+        assertListValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS),
+                INDEX_ADDRESS_WORK);
+        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_HOME_ADDRESS), false);
+        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_WORK_ADDRESS), true);
+        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_SAVE_CC), false);
+    }
+
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testCustomizedSaveUi() throws Exception {
+        customizedSaveUi(false);
+    }
+
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testCustomizedSaveUiWithContentDescription() throws Exception {
+        customizedSaveUi(true);
+    }
+
+    /**
+     * Tests that a spinner can be used on custom save descriptions.
+     */
+    private void customizedSaveUi(boolean withContentDescription) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final String packageName = getContext().getPackageName();
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD, ID_CC_NUMBER, ID_CC_EXPIRATION)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final RemoteViews presentation = new RemoteViews(packageName,
+                            R.layout.two_horizontal_text_fields);
+                    final FillContext context = contexts.get(0);
+                    final AutofillId ccNumberId = findAutofillIdByResourceId(context,
+                            ID_CC_NUMBER);
+                    final AutofillId ccExpirationId = findAutofillIdByResourceId(context,
+                            ID_CC_EXPIRATION);
+                    final CharSequenceTransformation trans1 = new CharSequenceTransformation
+                            .Builder(ccNumberId, Pattern.compile("(.*)"), "$1")
+                            .build();
+                    final CharSequenceTransformation trans2 = new CharSequenceTransformation
+                            .Builder(ccExpirationId, Pattern.compile("(.*)"), "$1")
+                            .build();
+                    final ImageTransformation trans3 = (withContentDescription
+                            ? new ImageTransformation.Builder(ccNumberId,
+                                    Pattern.compile("(.*)"), R.drawable.android,
+                                    "One image is worth thousand words")
+                            : new ImageTransformation.Builder(ccNumberId,
+                                    Pattern.compile("(.*)"), R.drawable.android))
+                            .build();
+
+                    final CustomDescription customDescription =
+                            new CustomDescription.Builder(presentation)
+                            .addChild(R.id.first, trans1)
+                            .addChild(R.id.second, trans2)
+                            .addChild(R.id.img, trans3)
+                            .build();
+                    builder.setCustomDescription(customDescription);
+                })
+                .build());
+
+        // Dynamically change view contents
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onCcNumber((v) -> v.setText("4815162342"));
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
+        mActivity.tapBuy();
+
+        // First make sure the UI is shown...
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
+
+        // Then make sure it does have the custom views on it...
+        final UiObject2 staticText = saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT));
+        assertThat(staticText).isNotNull();
+        assertThat(staticText.getText()).isEqualTo("YO:");
+
+        final UiObject2 number = saveUi.findObject(By.res(packageName, "first"));
+        assertThat(number).isNotNull();
+        assertThat(number.getText()).isEqualTo("4815162342");
+
+        final UiObject2 expiration = saveUi.findObject(By.res(packageName, "second"));
+        assertThat(expiration).isNotNull();
+        assertThat(expiration.getText()).isEqualTo("today");
+
+        final UiObject2 image = saveUi.findObject(By.res(packageName, "img"));
+        assertThat(image).isNotNull();
+        final String contentDescription = image.getContentDescription();
+        if (withContentDescription) {
+            assertThat(contentDescription).isEqualTo("One image is worth thousand words");
+        } else {
+            assertThat(contentDescription).isNull();
+        }
+    }
+
+    /**
+     * Tests that a custom save description is ignored when the selected spinner element is not
+     * available in the autofill options.
+     */
+    @Test
+    public void testCustomizedSaveUiWhenListResolutionFails() throws Exception {
+        // Set service.
+        enableService();
+
+        // Change spinner to return just one item so the transformation throws an exception when
+        // fetching it.
+        mActivity.getCcExpirationAdapter().setAutofillOptions("D'OH!");
+
+        // Set expectations.
+        final String packageName = getContext().getPackageName();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD, ID_CC_NUMBER, ID_CC_EXPIRATION)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId ccNumberId = findAutofillIdByResourceId(context,
+                            ID_CC_NUMBER);
+                    final AutofillId ccExpirationId = findAutofillIdByResourceId(context,
+                            ID_CC_EXPIRATION);
+                    final RemoteViews presentation = new RemoteViews(packageName,
+                            R.layout.two_horizontal_text_fields);
+                    final CharSequenceTransformation trans1 = new CharSequenceTransformation
+                            .Builder(ccNumberId, Pattern.compile("(.*)"), "$1")
+                            .build();
+                    final CharSequenceTransformation trans2 = new CharSequenceTransformation
+                            .Builder(ccExpirationId, Pattern.compile("(.*)"), "$1")
+                            .build();
+                    final CustomDescription customDescription =
+                            new CustomDescription.Builder(presentation)
+                            .addChild(R.id.first, trans1)
+                            .addChild(R.id.second, trans2)
+                            .build();
+                    builder.setCustomDescription(customDescription);
+                })
+                .build());
+
+        // Dynamically change view contents
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onCcNumber((v) -> v.setText("4815162342"));
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
+        mActivity.tapBuy();
+
+        // First make sure the UI is shown...
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
+
+        // Then make sure it does not have the custom views on it...
+        assertThat(saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT))).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify EditText by setting with AutofillValue.
+    // ============================================================================================
+    @Test
+    public void autofillValidTextValue() throws Exception {
+        autofillEditText(AutofillValue.forText("filled"), "filled", true);
+    }
+
+    @Test
+    public void autofillEmptyTextValue() throws Exception {
+        autofillEditText(AutofillValue.forText(""), "", true);
+    }
+
+    @Test
+    public void autofillTextWithListValue() throws Exception {
+        autofillEditText(AutofillValue.forList(0), "", false);
+    }
+
+    private void autofillEditText(AutofillValue value, String expectedText,
+            boolean expectAutoFill) throws Exception {
+        // Enable service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_CC_NUMBER, value)
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it and check the result.
+        EditText editText = mActivity.getCcNumber();
+        OneTimeTextWatcher textWatcher = new OneTimeTextWatcher(ID_CC_NUMBER, editText,
+                expectedText);
+        editText.addTextChangedListener(textWatcher);
+        mUiBot.selectDataset("dataset");
+
+        if (expectAutoFill) {
+            textWatcher.assertAutoFilled();
+        } else {
+            assertThat(editText.getText().toString()).isEqualTo(expectedText);
+        }
+    }
+
+    @Test
+    public void getEditTextAutoFillValue() throws Exception {
+        EditText editText = mActivity.getCcNumber();
+        mActivity.syncRunOnUiThread(() -> editText.setText("test"));
+
+        assertThat(editText.getAutofillValue()).isEqualTo(AutofillValue.forText("test"));
+
+        mActivity.syncRunOnUiThread(() -> editText.setEnabled(false));
+
+        assertThat(editText.getAutofillValue()).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify CheckBox by setting with AutofillValue.
+    // ============================================================================================
+    @Test
+    public void autofillToggleValueWithTrue() throws Exception {
+        autofillCompoundButton(AutofillValue.forToggle(true), true, true);
+    }
+
+    @Test
+    public void autofillToggleValueWithFalse() throws Exception {
+        autofillCompoundButton(AutofillValue.forToggle(false), false, false);
+    }
+
+    @Test
+    public void autofillCompoundButtonWithTextValue() throws Exception {
+        autofillCompoundButton(AutofillValue.forText(""), false, false);
+    }
+
+    private void autofillCompoundButton(AutofillValue value, boolean expectedValue,
+            boolean expectAutoFill) throws Exception {
+        // Enable service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_SAVE_CC, value)
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        mActivity.onSaveCc((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it and check the result.
+        CheckBox compoundButton = mActivity.getSaveCc();
+        OneTimeCompoundButtonListener checkedWatcher = new OneTimeCompoundButtonListener(
+                ID_SAVE_CC, compoundButton, expectedValue);
+        compoundButton.setOnCheckedChangeListener(checkedWatcher);
+        mUiBot.selectDataset("dataset");
+
+        if (expectAutoFill) {
+            checkedWatcher.assertAutoFilled();
+        } else {
+            assertThat(compoundButton.isChecked()).isEqualTo(expectedValue);
+        }
+    }
+
+    @Test
+    public void getCompoundButtonAutoFillValue() throws Exception {
+        CheckBox compoundButton = mActivity.getSaveCc();
+        mActivity.syncRunOnUiThread(() -> compoundButton.setChecked(true));
+
+        assertThat(compoundButton.getAutofillValue()).isEqualTo(AutofillValue.forToggle(true));
+
+        mActivity.syncRunOnUiThread(() -> compoundButton.setEnabled(false));
+
+        assertThat(compoundButton.getAutofillValue()).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify Spinner by setting with AutofillValue
+    // ============================================================================================
+    private void autofillListValue(AutofillValue value, int expectedValue,
+            boolean expectAutoFill) throws Exception {
+        // Enable service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_CC_EXPIRATION, value)
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        mActivity.onCcExpiration((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it and check the result.
+        Spinner spinner = mActivity.getCcExpiration();
+        OneTimeSpinnerListener spinnerWatcher = new OneTimeSpinnerListener(
+                ID_CC_EXPIRATION, spinner, expectedValue);
+        spinner.setOnItemSelectedListener(spinnerWatcher);
+        mUiBot.selectDatasetSync("dataset");
+
+        if (expectAutoFill) {
+            spinnerWatcher.assertAutoFilled();
+        } else {
+            assertThat(spinner.getSelectedItemPosition()).isEqualTo(expectedValue);
+        }
+    }
+
+    @Test
+    public void autofillZeroListValueToSpinner() throws Exception {
+        autofillListValue(AutofillValue.forList(0), 0, false);
+    }
+
+    @Test
+    public void autofillOneListValueToSpinner() throws Exception {
+        autofillListValue(AutofillValue.forList(1), 1, true);
+    }
+
+    @Test
+    public void autofillInvalidListValueToSpinner() throws Exception {
+        autofillListValue(AutofillValue.forList(-1), 0, false);
+    }
+
+    @Test
+    public void autofillSpinnerWithTextValue() throws Exception {
+        autofillListValue(AutofillValue.forText(""), 0, false);
+    }
+
+    @Test
+    public void getSpinnerAutoFillValue() throws Exception {
+        Spinner spinner = mActivity.getCcExpiration();
+        mActivity.syncRunOnUiThread(() -> spinner.setSelection(1));
+
+        assertThat(spinner.getAutofillValue()).isEqualTo(AutofillValue.forList(1));
+
+        mActivity.syncRunOnUiThread(() -> spinner.setEnabled(false));
+
+        assertThat(spinner.getAutofillValue()).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify DatePicker by setting with AutofillValue
+    // ============================================================================================
+    @Test
+    public void autofillValidDateValueToDatePicker() throws Exception {
+        autofillDateValueToDatePicker(AutofillValue.forDate(getDateAsMillis(2017, 3, 7, 12, 32)),
+                true);
+    }
+
+    @Test
+    public void autofillDatePickerWithTextValue() throws Exception {
+        autofillDateValueToDatePicker(AutofillValue.forText(""), false);
+    }
+
+    private void autofillDateValueToDatePicker(AutofillValue value,
+            boolean expectAutoFill) throws Exception {
+        // Enable service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_DATE_PICKER, value)
+                .setField(ID_CC_NUMBER, "filled")
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        DatePicker datePicker = mActivity.getDatePicker();
+        int nonAutofilledYear = datePicker.getYear();
+        int nonAutofilledMonth = datePicker.getMonth();
+        int nonAutofilledDay = datePicker.getDayOfMonth();
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it and check the result.
+        OneTimeDateListener dateWatcher = new OneTimeDateListener(ID_DATE_PICKER, datePicker,
+                2017, 3, 7);
+        datePicker.setOnDateChangedListener(dateWatcher);
+        mUiBot.selectDataset("dataset");
+
+        if (expectAutoFill) {
+            dateWatcher.assertAutoFilled();
+        } else {
+            Helper.assertDateValue(datePicker, nonAutofilledYear, nonAutofilledMonth,
+                    nonAutofilledDay);
+        }
+    }
+
+    private long getDateAsMillis(int year, int month, int day, int hour, int minute) {
+        Calendar calendar = Calendar.getInstance(
+                mActivity.getResources().getConfiguration().getLocales().get(0));
+
+        calendar.set(year, month, day, hour, minute);
+
+        return calendar.getTimeInMillis();
+    }
+
+    @Test
+    public void getDatePickerAutoFillValue() throws Exception {
+        DatePicker datePicker = mActivity.getDatePicker();
+        mActivity.syncRunOnUiThread(() -> datePicker.updateDate(2017, 3, 7));
+
+        Helper.assertDateValue(datePicker, 2017, 3, 7);
+
+        mActivity.syncRunOnUiThread(() -> datePicker.setEnabled(false));
+
+        assertThat(datePicker.getAutofillValue()).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify TimePicker by setting with AutofillValue
+    // ============================================================================================
+    @Test
+    public void autofillValidDateValueToTimePicker() throws Exception {
+        autofillDateValueToTimePicker(AutofillValue.forDate(getDateAsMillis(2017, 3, 7, 12, 32)),
+                true);
+    }
+
+    @Test
+    public void autofillTimePickerWithTextValue() throws Exception {
+        autofillDateValueToTimePicker(AutofillValue.forText(""), false);
+    }
+
+    private void autofillDateValueToTimePicker(AutofillValue value,
+            boolean expectAutoFill) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_TIME_PICKER, value)
+                .setField(ID_CC_NUMBER, "filled")
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        TimePicker timePicker = mActivity.getTimePicker();
+        mActivity.syncRunOnUiThread(() -> {
+            timePicker.setIs24HourView(true);
+        });
+        int nonAutofilledHour = timePicker.getHour();
+        int nonAutofilledMinute = timePicker.getMinute();
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it and check the result.
+        MultipleTimesTimeListener timeWatcher = new MultipleTimesTimeListener(ID_TIME_PICKER, 1,
+                timePicker, 12, 32);
+        timePicker.setOnTimeChangedListener(timeWatcher);
+        mUiBot.selectDataset("dataset");
+
+        if (expectAutoFill) {
+            timeWatcher.assertAutoFilled();
+        } else {
+            Helper.assertTimeValue(timePicker, nonAutofilledHour, nonAutofilledMinute);
+        }
+    }
+
+    @Test
+    public void getTimePickerAutoFillValue() throws Exception {
+        TimePicker timePicker = mActivity.getTimePicker();
+        mActivity.syncRunOnUiThread(() -> {
+            timePicker.setHour(12);
+            timePicker.setMinute(32);
+        });
+
+        Helper.assertTimeValue(timePicker, 12, 32);
+
+        mActivity.syncRunOnUiThread(() -> timePicker.setEnabled(false));
+
+        assertThat(timePicker.getAutofillValue()).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify RadioGroup by setting with AutofillValue
+    // ============================================================================================
+    @Test
+    public void autofillZeroListValueToRadioGroup() throws Exception {
+        autofillRadioGroup(AutofillValue.forList(0), 0, false);
+    }
+
+    @Test
+    public void autofillOneListValueToRadioGroup() throws Exception {
+        autofillRadioGroup(AutofillValue.forList(1), 1, true);
+    }
+
+    @Test
+    public void autofillInvalidListValueToRadioGroup() throws Exception {
+        autofillRadioGroup(AutofillValue.forList(-1), 0, false);
+    }
+
+    @Test
+    public void autofillRadioGroupWithTextValue() throws Exception {
+        autofillRadioGroup(AutofillValue.forText(""), 0, false);
+    }
+
+    private void autofillRadioGroup(AutofillValue value, int expectedValue,
+            boolean expectAutoFill) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_ADDRESS, value)
+                .setField(ID_CC_NUMBER, "filled")
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        mActivity.onHomeAddress((v) -> v.setChecked(true));
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        RadioGroup radioGroup = mActivity.getAddress();
+        MultipleTimesRadioGroupListener radioGroupWatcher = new MultipleTimesRadioGroupListener(
+                ID_ADDRESS, 2, radioGroup, expectedValue);
+        radioGroup.setOnCheckedChangeListener(radioGroupWatcher);
+
+        // Autofill it and check the result.
+        mUiBot.selectDataset("dataset");
+
+        if (expectAutoFill) {
+            radioGroupWatcher.assertAutoFilled();
+        } else {
+            if (expectedValue == 0) {
+                mActivity.assertRadioButtonValue(/* homeAddrValue= */
+                        true, /* workAddrValue= */ false);
+            } else {
+                mActivity.assertRadioButtonValue(/* homeAddrValue= */
+                        false, /* workAddrValue= */true);
+            }
+        }
+    }
+
+    @Test
+    public void getRadioGroupAutoFillValue() throws Exception {
+        RadioGroup radioGroup = mActivity.getAddress();
+        mActivity.onWorkAddress((v) -> v.setChecked(true));
+
+        assertThat(radioGroup.getAutofillValue()).isEqualTo(AutofillValue.forList(1));
+
+        mActivity.syncRunOnUiThread(() -> radioGroup.setEnabled(false));
+
+        assertThat(radioGroup.getAutofillValue()).isNull();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatasetFilteringDropdownTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatasetFilteringDropdownTest.java
new file mode 100644
index 0000000..5553b42
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatasetFilteringDropdownTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import android.autofillservice.cts.commontests.DatasetFilteringTest;
+
+public class DatasetFilteringDropdownTest extends DatasetFilteringTest {
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerCalendarActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerCalendarActivityTest.java
new file mode 100644
index 0000000..e8801a3
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerCalendarActivityTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import android.autofillservice.cts.activities.DatePickerCalendarActivity;
+import android.autofillservice.cts.commontests.DatePickerTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "Unit test")
+public class DatePickerCalendarActivityTest extends DatePickerTestCase<DatePickerCalendarActivity> {
+
+    @Override
+    protected AutofillActivityTestRule<DatePickerCalendarActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DatePickerCalendarActivity>(
+                DatePickerCalendarActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerSpinnerActivityTest.java
new file mode 100644
index 0000000..da0aba7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerSpinnerActivityTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import android.autofillservice.cts.activities.DatePickerSpinnerActivity;
+import android.autofillservice.cts.commontests.DatePickerTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "Unit test")
+public class DatePickerSpinnerActivityTest extends DatePickerTestCase<DatePickerSpinnerActivity> {
+
+    @Override
+    protected AutofillActivityTestRule<DatePickerSpinnerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DatePickerSpinnerActivity>(
+                DatePickerSpinnerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/DialogLauncherActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DialogLauncherActivityTest.java
new file mode 100644
index 0000000..cc04c0c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DialogLauncherActivityTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+
+import android.autofillservice.cts.activities.DialogLauncherActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+
+import org.junit.Test;
+
+public class DialogLauncherActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<DialogLauncherActivity> {
+
+    private DialogLauncherActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<DialogLauncherActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DialogLauncherActivity>(DialogLauncherActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testAutofill_noDatasets() throws Exception {
+        autofillNoDatasetsTest(false);
+    }
+
+    @Test
+    public void testAutofill_noDatasets_afterResizing() throws Exception {
+        autofillNoDatasetsTest(true);
+    }
+
+    private void autofillNoDatasetsTest(boolean resize) throws Exception {
+        enableService();
+        mActivity.launchDialog(mUiBot);
+
+        if (resize) {
+            mActivity.maximizeDialog();
+        }
+
+        // Set expectations.
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Asserts results.
+        try {
+            mUiBot.assertNoDatasetsEver();
+            // Make sure nodes were properly generated.
+            assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+            assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+        } catch (AssertionError e) {
+            Helper.dumpStructure("D'OH!", fillRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    public void testAutofill_oneDataset() throws Exception {
+        autofillOneDatasetTest(false);
+    }
+
+    @Test
+    public void testAutofill_oneDataset_afterResizing() throws Exception {
+        autofillOneDatasetTest(true);
+    }
+
+    private void autofillOneDatasetTest(boolean resize) throws Exception {
+        enableService();
+        mActivity.launchDialog(mUiBot);
+
+        if (resize) {
+            mActivity.maximizeDialog();
+        }
+
+        // Set expectations.
+        mActivity.expectAutofill("dude", "sweet");
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        final UiObject2 picker = mUiBot.assertDatasets("The Dude");
+        if (!Helper.isAutofillWindowFullScreen(mActivity)) {
+            mActivity.assertInDialogBounds(picker.getVisibleBounds());
+        }
+
+        // Asserts results.
+        mUiBot.selectDataset("The Dude");
+        mActivity.assertAutofilled();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/FillEventHistoryTest.java
new file mode 100644
index 0000000..778f170
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/FillEventHistoryTest.java
@@ -0,0 +1,800 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.LoginActivity.BACKDOOR_USERNAME;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.NULL_DATASET_ID;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetShown;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForSaveShown;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.commontests.FillEventHistoryCommonTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.FillResponse;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test that uses {@link LoginActivity} to test {@link FillEventHistory}.
+ */
+@AppModeFull(reason = "Service-specific test")
+public class FillEventHistoryTest extends FillEventHistoryCommonTestCase {
+
+    @Test
+    public void testContextCommitted_whenServiceDidntDoAnything() throws Exception {
+        enableService();
+
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert no events where generated
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    @Test
+    public void textContextCommitted_withoutDatasets() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        sReplier.getNextSaveRequest();
+
+        // Assert it
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForSaveShown(events.get(0), NULL_DATASET_ID);
+    }
+
+
+    @Test
+    public void testContextCommitted_idlessDatasets() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username1", "password1");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+        }
+
+        // Finish the context by login in
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        final String expectedMessage = getWelcomeMessage("USERNAME");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+            assertFillEventForDatasetShown(events.get(2));
+        }
+    }
+
+    @Test
+    public void testContextCommitted_idlessDatasetSelected_datasetWithIdIgnored()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username1", "password1");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+        }
+
+        // Finish the context by login in
+        mActivity.onPassword((v) -> v.setText("username1"));
+
+        final String expectedMessage = getWelcomeMessage("username1");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+
+            FillEventHistory.Event event2 = events.get(2);
+            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event2.getDatasetId()).isNull();
+            assertThat(event2.getClientState()).isNull();
+            assertThat(event2.getSelectedDatasetIds()).isEmpty();
+            assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
+            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
+                    ID_PASSWORD);
+            final Map<AutofillId, String> changedFields = event2.getChangedFields();
+            assertThat(changedFields).containsExactly(passwordId, "id2");
+            assertThat(event2.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    @Test
+    public void testContextCommitted_idlessDatasetIgnored_datasetWithIdSelected()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username2", "password2");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset2");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+        }
+
+        // Finish the context by login in
+        mActivity.onPassword((v) -> v.setText("username2"));
+
+        final String expectedMessage = getWelcomeMessage("username2");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+
+            final FillEventHistory.Event event2 = events.get(2);
+            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event2.getDatasetId()).isNull();
+            assertThat(event2.getClientState()).isNull();
+            assertThat(event2.getSelectedDatasetIds()).containsExactly("id2");
+            assertThat(event2.getIgnoredDatasetIds()).isEmpty();
+            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
+                    ID_PASSWORD);
+            final Map<AutofillId, String> changedFields = event2.getChangedFields();
+            assertThat(changedFields).containsExactly(passwordId, "id2");
+            assertThat(event2.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, no dataset was selected by the user,
+     * neither the user entered values that were present in these datasets.
+     */
+    @Test
+    public void testContextCommitted_noDatasetSelected_valuesNotManuallyEntered() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("dataset1", "dataset2");
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetShown(events.get(0));
+        }
+        // Enter values not present at the datasets
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage("USERNAME");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Verify history again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            final Event event = events.get(1);
+            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event.getDatasetId()).isNull();
+            assertThat(event.getClientState()).isNull();
+            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event.getChangedFields()).isEmpty();
+            assertThat(event.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, just one dataset was selected by the user,
+     * and the user changed the values provided by the service.
+     */
+    @Test
+    public void testContextCommitted_oneDatasetSelected() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username1", "password1");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+        }
+
+        // Finish the context by login in
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        final String expectedMessage = getWelcomeMessage("USERNAME");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+
+            assertFillEventForDatasetShown(events.get(2));
+            final FillEventHistory.Event event2 = events.get(3);
+            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event2.getDatasetId()).isNull();
+            assertThat(event2.getClientState()).isNull();
+            assertThat(event2.getSelectedDatasetIds()).containsExactly("id1");
+            assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
+            final Map<AutofillId, String> changedFields = event2.getChangedFields();
+            final FillContext context = request.contexts.get(0);
+            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
+            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+
+            assertThat(changedFields).containsExactlyEntriesIn(
+                    ImmutableMap.of(usernameId, "id1", passwordId, "id1"));
+            assertThat(event2.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, both datasets were selected by the user,
+     * and the user changed the values provided by the service.
+     */
+    @Test
+    public void testContextCommitted_multipleDatasetsSelected() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_PASSWORD, "password")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+
+        // Autofill username
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+        }
+
+        // Autofill password
+        mActivity.expectPasswordAutoFill("password");
+
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.selectDataset("dataset2");
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
+        }
+
+        // Finish the context by login in
+        mActivity.onPassword((v) -> v.setText("username"));
+
+        final String expectedMessage = getWelcomeMessage("username");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(6);
+
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
+
+            assertFillEventForDatasetShown(events.get(4));
+            final FillEventHistory.Event event3 = events.get(5);
+            assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event3.getDatasetId()).isNull();
+            assertThat(event3.getClientState()).isNull();
+            assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event3.getIgnoredDatasetIds()).isEmpty();
+            final Map<AutofillId, String> changedFields = event3.getChangedFields();
+            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
+                    ID_PASSWORD);
+            assertThat(changedFields).containsExactly(passwordId, "id2");
+            assertThat(event3.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, both datasets were selected by the user,
+     * and the user didn't change the values provided by the service.
+     */
+    @Test
+    public void testContextCommitted_multipleDatasetsSelected_butNotChanged() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill(BACKDOOR_USERNAME);
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Autofill username
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+        }
+
+        // Autofill password
+        mActivity.expectPasswordAutoFill("whatever");
+
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.selectDataset("dataset2");
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
+        }
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(5);
+
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
+
+            final FillEventHistory.Event event3 = events.get(4);
+            assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event3.getDatasetId()).isNull();
+            assertThat(event3.getClientState()).isNull();
+            assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event3.getIgnoredDatasetIds()).isEmpty();
+            assertThat(event3.getChangedFields()).isEmpty();
+            assertThat(event3.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, the user selected the dataset, than changed
+     * the autofilled values, but then change the values again so they match what was provided by
+     * the service.
+     */
+    @Test
+    public void testContextCommitted_oneDatasetSelected_Changed_thenChangedBack()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username")
+                        .setField(ID_PASSWORD, "username")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username", "username");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+        }
+
+        // Change the fields to different values from0 datasets
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        // Then change back to dataset values
+        mActivity.onUsername((v) -> v.setText("username"));
+        mActivity.onPassword((v) -> v.setText("username"));
+
+        final String expectedMessage = getWelcomeMessage("username");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+
+            FillEventHistory.Event event4 = events.get(3);
+            assertThat(event4.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event4.getDatasetId()).isNull();
+            assertThat(event4.getClientState()).isNull();
+            assertThat(event4.getSelectedDatasetIds()).containsExactly("id1");
+            assertThat(event4.getIgnoredDatasetIds()).isEmpty();
+            assertThat(event4.getChangedFields()).isEmpty();
+            assertThat(event4.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, the user did not selected any dataset, but
+     * the user manually entered values that match what was provided by the service.
+     */
+    @Test
+    public void testContextCommitted_noDatasetSelected_butManuallyEntered()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setField(ID_PASSWORD, "NotUsedPassword")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "NotUserUsername")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("dataset1", "dataset2");
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetShown(events.get(0));
+        }
+
+        // Enter values present at the datasets
+        mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+        mActivity.onPassword((v) -> v.setText("whatever"));
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            FillEventHistory.Event event = events.get(1);
+            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event.getDatasetId()).isNull();
+            assertThat(event.getClientState()).isNull();
+            assertThat(event.getSelectedDatasetIds()).isEmpty();
+            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event.getChangedFields()).isEmpty();
+            final FillContext context = request.contexts.get(0);
+            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
+            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+
+            final Map<AutofillId, Set<String>> manuallyEnteredFields =
+                    event.getManuallyEnteredField();
+            assertThat(manuallyEnteredFields).isNotNull();
+            assertThat(manuallyEnteredFields.size()).isEqualTo(2);
+            assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1");
+            assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2");
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, the user did not selected any dataset, but
+     * the user manually entered values that match what was provided by the service on different
+     * datasets.
+     */
+    @Test
+    public void testContextCommitted_noDatasetSelected_butManuallyEntered_matchingMultipleDatasets()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setField(ID_PASSWORD, "NotUsedPassword")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "NotUserUsername")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id3")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset3"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("dataset1", "dataset2", "dataset3");
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetShown(events.get(0));
+        }
+
+        // Enter values present at the datasets
+        mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+        mActivity.onPassword((v) -> v.setText("whatever"));
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+
+            final FillEventHistory.Event event = events.get(1);
+            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event.getDatasetId()).isNull();
+            assertThat(event.getClientState()).isNull();
+            assertThat(event.getSelectedDatasetIds()).isEmpty();
+            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2", "id3");
+            assertThat(event.getChangedFields()).isEmpty();
+            final FillContext context = request.contexts.get(0);
+            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
+            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+
+            final Map<AutofillId, Set<String>> manuallyEnteredFields =
+                    event.getManuallyEnteredField();
+            assertThat(manuallyEnteredFields.size()).isEqualTo(2);
+            assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1", "id3");
+            assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2", "id3");
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/InitializedCheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/InitializedCheckoutActivityTest.java
new file mode 100644
index 0000000..4e23b52
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/InitializedCheckoutActivityTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_ADDRESS;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_CC_EXPIRATION;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_CC_NUMBER;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_SAVE_CC;
+import static android.autofillservice.cts.activities.CheckoutActivity.INDEX_ADDRESS_HOME;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.assertListValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertToggleValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+
+import android.autofillservice.cts.activities.InitializedCheckoutActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.platform.test.annotations.AppModeFull;
+
+import org.junit.Test;
+
+/**
+ * Test case for an activity containing non-TextField views with initial values set on XML.
+ */
+@AppModeFull(reason = "CheckoutActivityTest() is enough")
+public class InitializedCheckoutActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<InitializedCheckoutActivity> {
+
+    private InitializedCheckoutActivity mCheckoutActivity;
+
+    @Override
+    protected AutofillActivityTestRule<InitializedCheckoutActivity> getActivityRule() {
+        return new AutofillActivityTestRule<InitializedCheckoutActivity>(
+                InitializedCheckoutActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mCheckoutActivity = getActivity();
+            }
+        };
+
+    }
+
+    @Test
+    public void testSanitization() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger auto-fill.
+        mCheckoutActivity.onCcNumber((v) -> v.requestFocus());
+
+        // Assert sanitization: most everything should be available...
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_CC_NUMBER), "4815162342");
+        assertListValue(findNodeByResourceId(fillRequest.structure, ID_ADDRESS),
+                INDEX_ADDRESS_HOME);
+        assertToggleValue(findNodeByResourceId(fillRequest.structure, ID_SAVE_CC), true);
+
+        // ... except Spinner, whose initial value cannot be set by resources:
+        assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java
new file mode 100644
index 0000000..452d4f5
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java
@@ -0,0 +1,2921 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.LoginActivity.AUTHENTICATION_MESSAGE;
+import static android.autofillservice.cts.activities.LoginActivity.BACKDOOR_USERNAME;
+import static android.autofillservice.cts.activities.LoginActivity.ID_USERNAME_CONTAINER;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.FAIL;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_CANCEL_FILL;
+import static android.autofillservice.cts.testcore.Helper.ID_EMPTY;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.allowOverlays;
+import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.assertNumberOfChildren;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertTextOnly;
+import static android.autofillservice.cts.testcore.Helper.assertValue;
+import static android.autofillservice.cts.testcore.Helper.assertViewAutofillState;
+import static android.autofillservice.cts.testcore.Helper.disallowOverlays;
+import static android.autofillservice.cts.testcore.Helper.dumpStructure;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.isAutofillWindowFullScreen;
+import static android.autofillservice.cts.testcore.Helper.setUserComplete;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.SERVICE_CLASS;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.SERVICE_PACKAGE;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.isConnected;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilDisconnected;
+import static android.content.Context.CLIPBOARD_SERVICE;
+import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+import static android.text.InputType.TYPE_NULL;
+import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+
+import static com.android.compatibility.common.util.ShellUtils.sendKeyEvent;
+import static com.android.compatibility.common.util.ShellUtils.tap;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.PendingIntent;
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.DummyActivity;
+import android.autofillservice.cts.activities.EmptyActivity;
+import android.autofillservice.cts.commontests.LoginActivityCommonTestCase;
+import android.autofillservice.cts.testcore.BadAutofillService;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.DismissType;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.NoOpAutofillService;
+import android.autofillservice.cts.testcore.OneTimeCancellationSignalListener;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.FillContext;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.FlakyTest;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This is the test case covering most scenarios - other test cases will cover characteristics
+ * specific to that test's activity (for example, custom views).
+ */
+public class LoginActivityTest extends LoginActivityCommonTestCase {
+
+    private static final String TAG = "LoginActivityTest";
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillAutomaticallyAfterServiceReturnedNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Try again, in a field that was added after the first request
+        final EditText child = new EditText(mActivity);
+        child.setId(R.id.empty);
+        mActivity.addChild(child);
+        final OneTimeTextWatcher watcher = new OneTimeTextWatcher("child", child,
+                "new view on the block");
+        child.addTextChangedListener(watcher);
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setField(ID_EMPTY, "new view on the block")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.syncRunOnUiThread(() -> child.requestFocus());
+
+        sReplier.getNextFillRequest();
+
+        // Select the dataset.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        watcher.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillManuallyAfterServiceReturnedNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Try again, forcing it
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+
+        mActivity.forceAutofillOnUsername();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
+
+        // Select the dataset.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillManuallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        // NOTE: must be on password, as saveOnlyTest() will trigger on username
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+        sReplier.assertNoUnhandledFillRequests();
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.assertNoDatasetsEver();
+        sReplier.assertNoUnhandledFillRequests();
+
+        // Try again, forcing it
+        saveOnlyTest(/* manually= */ true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillAutomaticallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Try again, in a field that was added after the first request
+        final EditText child = new EditText(mActivity);
+        child.setId(R.id.empty);
+        mActivity.addChild(child);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        ID_USERNAME,
+                        ID_PASSWORD,
+                        ID_EMPTY)
+                .build());
+        mActivity.syncRunOnUiThread(() -> child.requestFocus());
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        mActivity.runOnUiThread(() -> child.setText("NOT MR.M"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        sReplier.assertNoUnhandledSaveRequests();
+        assertThat(saveRequest.datasetIds).isNull();
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(username, "malkovich");
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+        assertTextAndValue(password, "malkovich");
+        final ViewNode childNode = findNodeByResourceId(saveRequest.structure, ID_EMPTY);
+        assertTextAndValue(childNode, "NOT MR.M");
+    }
+
+    /**
+     * More detailed test of what should happen after a service returns a {@code null} FillResponse:
+     * views that have already been visit should not trigger a new session, unless a manual autofill
+     * workflow was requested.
+     */
+    @Test
+    @AppModeFull(reason = "testAutoFillNoDatasets() is enough")
+    public void testMultipleIterationsAfterServiceReturnedNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger autofill on username - should call service
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        waitUntilDisconnected();
+
+        // Every other call should be ignored
+        mActivity.onPassword(View::requestFocus);
+        mActivity.onUsername(View::requestFocus);
+        mActivity.onPassword(View::requestFocus);
+
+        // Trigger autofill by manually requesting username - should call service
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.forceAutofillOnUsername();
+        final FillRequest manualRequest1 = sReplier.getNextFillRequest();
+        assertHasFlags(manualRequest1.flags, FLAG_MANUAL_REQUEST);
+        waitUntilDisconnected();
+
+        // Trigger autofill by manually requesting password - should call service
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.forceAutofillOnPassword();
+        final FillRequest manualRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(manualRequest2.flags, FLAG_MANUAL_REQUEST);
+        waitUntilDisconnected();
+    }
+
+    @FlakyTest(bugId = 162372863)
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyAlwaysCallServiceAgain() throws Exception {
+        // Set service.
+        enableService();
+
+        // First request
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.onUsername(View::requestFocus);
+        // Waits for the fill request to be sent to the autofill service
+        mUiBot.waitForIdleSync();
+
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Second request
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "DUDE")
+                .setField(ID_PASSWORD, "SWEET")
+                .setPresentation(createPresentation("THE DUDE"))
+                .build());
+
+        mActivity.forceAutofillOnUsername();
+        mUiBot.waitForIdleSync();
+
+        final FillRequest secondRequest = sReplier.getNextFillRequest();
+        assertHasFlags(secondRequest.flags, FLAG_MANUAL_REQUEST);
+        mUiBot.assertDatasets("THE DUDE");
+    }
+
+    @Test
+    public void testAutoFillOneDataset() throws Exception {
+        autofillOneDatasetTest(BorderType.NONE);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset_withHeaderAndFooter() is enough")
+    public void testAutoFillOneDataset_withHeader() throws Exception {
+        autofillOneDatasetTest(BorderType.HEADER_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset_withHeaderAndFooter() is enough")
+    public void testAutoFillOneDataset_withFooter() throws Exception {
+        autofillOneDatasetTest(BorderType.FOOTER_ONLY);
+    }
+
+    @Test
+    public void testAutoFillOneDataset_withHeaderAndFooter() throws Exception {
+        autofillOneDatasetTest(BorderType.BOTH);
+    }
+
+    private enum BorderType {
+        NONE,
+        HEADER_ONLY,
+        FOOTER_ONLY,
+        BOTH
+    }
+
+    private void autofillOneDatasetTest(BorderType borderType) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        String expectedHeader = null, expectedFooter = null;
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build());
+        if (borderType == BorderType.BOTH || borderType == BorderType.HEADER_ONLY) {
+            expectedHeader = "Head";
+            builder.setHeader(createPresentation(expectedHeader));
+        }
+        if (borderType == BorderType.BOTH || borderType == BorderType.FOOTER_ONLY) {
+            expectedFooter = "Tails";
+            builder.setFooter(createPresentation(expectedFooter));
+        }
+        sReplier.addResponse(builder.build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Dynamically set password to make sure it's sanitized.
+        mActivity.onPassword((v) -> v.setText("I AM GROOT"));
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Auto-fill it.
+        final UiObject2 picker = mUiBot.assertDatasetsWithBorders(expectedHeader, expectedFooter,
+                "The Dude");
+
+        mUiBot.selectDataset(picker, "The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Validation checks.
+
+        // Make sure input was sanitized.
+        final FillRequest request = sReplier.getNextFillRequest();
+        assertWithMessage("CancelationSignal is null").that(request.cancellationSignal).isNotNull();
+        assertTextIsSanitized(request.structure, ID_PASSWORD);
+        final FillContext fillContext = request.contexts.get(request.contexts.size() - 1);
+        assertThat(fillContext.getFocusedId())
+                .isEqualTo(findAutofillIdByResourceId(fillContext, ID_USERNAME));
+
+        // Make sure initial focus was properly set.
+        assertWithMessage("Username node is not focused").that(
+                findNodeByResourceId(request.structure, ID_USERNAME).isFocused()).isTrue();
+        assertWithMessage("Password node is focused").that(
+                findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
+    }
+
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillAgainAfterOnFailure() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(FAIL);
+
+        // Trigger autofill.
+        requestFocusOnUsernameNoWindowChange();
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Try again
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build());
+        sReplier.addResponse(builder.build());
+
+        // Trigger autofill.
+        clearFocus();
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        mActivity.expectAutoFill("dude", "sweet");
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetPickerPosition() throws Exception {
+        final boolean pickerAndViewBoundsMatches = !isAutofillWindowFullScreen(mContext);
+
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final View username = mActivity.getUsername();
+        final View password = mActivity.getPassword();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
+                        .setField(ID_PASSWORD, "sweet", createPresentation("SWEET"))
+                        .build());
+        sReplier.addResponse(builder.build());
+
+        // Trigger autofill on username
+        final Rect usernameBoundaries1 = mUiBot.selectByRelativeId(ID_USERNAME).getVisibleBounds();
+        sReplier.getNextFillRequest();
+        callback.assertUiShownEvent(username);
+        final Rect usernamePickerBoundaries1 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
+        Log.v(TAG,
+                "Username1 at " + usernameBoundaries1 + "; picker at " + usernamePickerBoundaries1);
+        // TODO(b/37566627): assertions below might be too aggressive - use range instead?
+        if (pickerAndViewBoundsMatches) {
+            if (usernamePickerBoundaries1.top < usernameBoundaries1.bottom) {
+                assertThat(usernamePickerBoundaries1.bottom).isEqualTo(usernameBoundaries1.top);
+            } else {
+                assertThat(usernamePickerBoundaries1.top).isEqualTo(usernameBoundaries1.bottom);
+            }
+
+            assertThat(usernamePickerBoundaries1.left).isEqualTo(usernameBoundaries1.left);
+        }
+
+        // Move to password
+        final Rect passwordBoundaries1 = mUiBot.selectByRelativeId(ID_PASSWORD).getVisibleBounds();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+        final Rect passwordPickerBoundaries1 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
+        Log.v(TAG,
+                "Password1 at " + passwordBoundaries1 + "; picker at " + passwordPickerBoundaries1);
+        // TODO(b/37566627): assertions below might be too aggressive - use range instead?
+        if (pickerAndViewBoundsMatches) {
+            if (passwordPickerBoundaries1.top < passwordBoundaries1.bottom) {
+                assertThat(passwordPickerBoundaries1.bottom).isEqualTo(passwordBoundaries1.top);
+            } else {
+                assertThat(passwordPickerBoundaries1.top).isEqualTo(passwordBoundaries1.bottom);
+            }
+            assertThat(passwordPickerBoundaries1.left).isEqualTo(passwordBoundaries1.left);
+        }
+
+        // Then back to username
+        final Rect usernameBoundaries2 = mUiBot.selectByRelativeId(ID_USERNAME).getVisibleBounds();
+        callback.assertUiHiddenEvent(password);
+        callback.assertUiShownEvent(username);
+        final Rect usernamePickerBoundaries2 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
+        Log.v(TAG,
+                "Username2 at " + usernameBoundaries2 + "; picker at " + usernamePickerBoundaries2);
+
+        // And back to the password again..
+        final Rect passwordBoundaries2 = mUiBot.selectByRelativeId(ID_PASSWORD).getVisibleBounds();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+        final Rect passwordPickerBoundaries2 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
+        Log.v(TAG,
+                "Password2 at " + passwordBoundaries2 + "; picker at " + passwordPickerBoundaries2);
+
+        // Assert final state matches initial...
+        // ... for username
+        assertWithMessage("Username2 at %s; Username1 at %s", usernameBoundaries2,
+                usernamePickerBoundaries1).that(usernameBoundaries2).isEqualTo(usernameBoundaries1);
+        assertWithMessage("Username2 picker at %s; Username1 picker at %s",
+                usernamePickerBoundaries2, usernamePickerBoundaries1).that(
+                usernamePickerBoundaries2).isEqualTo(usernamePickerBoundaries1);
+
+        // ... for password
+        assertWithMessage("Password2 at %s; Password1 at %s", passwordBoundaries2,
+                passwordBoundaries1).that(passwordBoundaries2).isEqualTo(passwordBoundaries1);
+        assertWithMessage("Password2 picker at %s; Password1 picker at %s",
+                passwordPickerBoundaries2, passwordPickerBoundaries1).that(
+                passwordPickerBoundaries2).isEqualTo(passwordPickerBoundaries1);
+
+        // Final validation check
+        callback.assertNumberUnhandledEvents(0);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillTwoDatasetsSameNumberOfFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .setPresentation(createPresentation("THE DUDE"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure all datasets are available...
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
+
+        // ... on all fields.
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillTwoDatasetsUnevenNumberOfFieldsFillsAll() throws Exception {
+        autoFillTwoDatasetsUnevenNumberOfFieldsTest(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillTwoDatasetsUnevenNumberOfFieldsFillsOne() throws Exception {
+        autoFillTwoDatasetsUnevenNumberOfFieldsTest(false);
+    }
+
+    private void autoFillTwoDatasetsUnevenNumberOfFieldsTest(boolean fillsAll) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setPresentation(createPresentation("THE DUDE"))
+                        .build())
+                .build());
+        if (fillsAll) {
+            mActivity.expectAutoFill("dude", "sweet");
+        } else {
+            mActivity.expectAutoFill("DUDE");
+        }
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure all datasets are available on username...
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
+
+        // ... but just one for password
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("The Dude");
+
+        // Auto-fill it.
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
+        if (fillsAll) {
+            mUiBot.selectDataset("The Dude");
+        } else {
+            mUiBot.selectDataset("THE DUDE");
+        }
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillDatasetWithoutFieldIsIgnored() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .build())
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure all datasets are available...
+        mUiBot.assertDatasets("The Dude");
+
+        // ... on all fields.
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("The Dude");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutoFillWhenViewHasChildAccessibilityNodes() throws Exception {
+        mActivity.onUsername((v) -> v.setAccessibilityDelegate(new AccessibilityDelegate() {
+            @Override
+            public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+                return new AccessibilityNodeProvider() {
+                    @Override
+                    public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+                        final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+                        if (virtualViewId == View.NO_ID) {
+                            info.addChild(v, 108);
+                        }
+                        return info;
+                    }
+                };
+            }
+        }));
+
+        testAutoFillOneDataset();
+    }
+
+    @Test
+    public void testAutoFillOneDatasetAndMoveFocusAround() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure tapping on other fields from the dataset does not trigger it again
+        requestFocusOnPassword();
+        sReplier.assertNoUnhandledFillRequests();
+
+        requestFocusOnUsername();
+        sReplier.assertNoUnhandledFillRequests();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Make sure tapping on other fields from the dataset does not trigger it again
+        requestFocusOnPassword();
+        mUiBot.assertNoDatasets();
+        requestFocusOnUsernameNoWindowChange();
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    public void testUiNotShownAfterAutofilled() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Make sure tapping on autofilled field does not trigger it again
+        requestFocusOnPassword();
+        mUiBot.assertNoDatasets();
+
+        requestFocusOnUsernameNoWindowChange();
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    public void testAutofillTapOutside() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("The Dude");
+
+        // tapping outside autofill window should close it and raise ui hidden event
+        mUiBot.waitForWindowChange(() -> tap(mActivity.getUsernameLabel()));
+        callback.assertUiHiddenEvent(username);
+
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testAutofillCallbacks() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        final View password = mActivity.getPassword();
+
+        callback.assertUiShownEvent(username);
+
+        requestFocusOnPassword();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+
+        // Unregister callback to make sure no more events are received
+        mActivity.unregisterCallback();
+        requestFocusOnUsername();
+        // Blindly sleep - we cannot wait on any event as none should have been sent
+        SystemClock.sleep(MyAutofillCallback.MY_TIMEOUT.ms());
+        callback.assertNumberUnhandledEvents(0);
+
+        // Autofill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackDisabled() throws Exception {
+        // Set service.
+        disableService();
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Assert callback was called
+        final View username = mActivity.getUsername();
+        callback.assertUiUnavailableEvent(username);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackNoDatasets() throws Exception {
+        callbackUnavailableTest(NO_RESPONSE);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackNoDatasetsButSaveInfo() throws Exception {
+        callbackUnavailableTest(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+    }
+
+    private void callbackUnavailableTest(CannedFillResponse response) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(response);
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        mUiBot.assertNoDatasetsEver();
+
+        // Assert callback was called
+        final View username = mActivity.getUsername();
+        callback.assertUiUnavailableEvent(username);
+    }
+
+    @Test
+    public void testAutoFillOneDatasetAndSave() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Bundle extras = new Bundle();
+        extras.putString("numbers", "4815162342");
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setId("I'm the alpha and the omega")
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setExtras(extras)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Since this is a Presubmit test, wait for connection to avoid flakiness.
+        waitUntilConnected();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Make sure input was sanitized...
+        assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+
+        // ...but labels weren't
+        assertTextOnly(fillRequest.structure, ID_USERNAME_LABEL, "Username");
+        assertTextOnly(fillRequest.structure, ID_PASSWORD_LABEL, "Password");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        assertViewAutofillState(mActivity.getPassword(), true);
+
+        // Try to login, it will fail.
+        final String loginMessage = mActivity.tapLogin();
+
+        assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(AUTHENTICATION_MESSAGE);
+
+        // Set right password...
+        mActivity.onPassword((v) -> v.setText("dude"));
+        assertViewAutofillState(mActivity.getPassword(), false);
+
+        // ... and try again
+        final String expectedMessage = getWelcomeMessage("dude");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
+
+        // Assert value of expected fields - should not be sanitized.
+        assertTextAndValue(saveRequest.structure, ID_USERNAME, "dude");
+        assertTextAndValue(saveRequest.structure, ID_PASSWORD, "dude");
+        assertTextOnly(saveRequest.structure, ID_USERNAME_LABEL, "Username");
+        assertTextOnly(saveRequest.structure, ID_PASSWORD_LABEL, "Password");
+
+        // Make sure extras were passed back on onSave()
+        assertThat(saveRequest.data).isNotNull();
+        final String extraValue = saveRequest.data.getString("numbers");
+        assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
+    }
+
+    @Test
+    public void testAutoFillOneDatasetAndSaveHidingOverlays() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Bundle extras = new Bundle();
+        extras.putString("numbers", "4815162342");
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setExtras(extras)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Since this is a Presubmit test, wait for connection to avoid flakiness.
+        waitUntilConnected();
+
+        sReplier.getNextFillRequest();
+
+        // Add an overlay on top of the whole screen
+        final View[] overlay = new View[1];
+        try {
+            // Allow ourselves to add overlays
+            allowOverlays();
+
+            // Make sure the fill UI is shown.
+            mUiBot.assertDatasets("The Dude");
+
+            final CountDownLatch latch = new CountDownLatch(1);
+
+            mActivity.runOnUiThread(() -> {
+                // This overlay is focusable, full-screen, which should block interaction
+                // with the fill UI unless the platform successfully hides overlays.
+                final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+                params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+                params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+                params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+                params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+
+                final View view = new View(mContext) {
+                    @Override
+                    protected void onAttachedToWindow() {
+                        super.onAttachedToWindow();
+                        latch.countDown();
+                    }
+                };
+                view.setBackgroundColor(Color.RED);
+                WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+                windowManager.addView(view, params);
+                overlay[0] = view;
+            });
+
+            // Wait for the window being added.
+            assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
+
+            // Auto-fill it.
+            mUiBot.selectDataset("The Dude");
+
+            // Check the results.
+            mActivity.assertAutoFilled();
+
+            // Try to login, it will fail.
+            final String loginMessage = mActivity.tapLogin();
+
+            assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(
+                    AUTHENTICATION_MESSAGE);
+
+            // Set right password...
+            mActivity.onPassword((v) -> v.setText("dude"));
+
+            // ... and try again
+            final String expectedMessage = getWelcomeMessage("dude");
+            final String actualMessage = mActivity.tapLogin();
+            assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+            // Assert the snack bar is shown and tap "Save".
+            mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+            final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+            // Assert value of expected fields - should not be sanitized.
+            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+            assertTextAndValue(username, "dude");
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+            assertTextAndValue(password, "dude");
+
+            // Make sure extras were passed back on onSave()
+            assertThat(saveRequest.data).isNotNull();
+            final String extraValue = saveRequest.data.getString("numbers");
+            assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
+        } finally {
+            try {
+                // Make sure we can no longer add overlays
+                disallowOverlays();
+                // Make sure the overlay is removed
+                mActivity.runOnUiThread(() -> {
+                    WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+                    windowManager.removeView(overlay[0]);
+                });
+            } catch (Exception e) {
+                mSafeCleanerRule.add(e);
+            }
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillMultipleDatasetsPickFirst() throws Exception {
+        multipleDatasetsTest(1);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillMultipleDatasetsPickSecond() throws Exception {
+        multipleDatasetsTest(2);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillMultipleDatasetsPickThird() throws Exception {
+        multipleDatasetsTest(3);
+    }
+
+    private void multipleDatasetsTest(int number) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "mr_plow")
+                        .setField(ID_PASSWORD, "D'OH!")
+                        .setPresentation(createPresentation("Mr Plow"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "el barto")
+                        .setField(ID_PASSWORD, "aycaramba!")
+                        .setPresentation(createPresentation("El Barto"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "mr sparkle")
+                        .setField(ID_PASSWORD, "Aw3someP0wer")
+                        .setPresentation(createPresentation("Mr Sparkle"))
+                        .build())
+                .build());
+        final String name;
+
+        switch (number) {
+            case 1:
+                name = "Mr Plow";
+                mActivity.expectAutoFill("mr_plow", "D'OH!");
+                break;
+            case 2:
+                name = "El Barto";
+                mActivity.expectAutoFill("el barto", "aycaramba!");
+                break;
+            case 3:
+                name = "Mr Sparkle";
+                mActivity.expectAutoFill("mr sparkle", "Aw3someP0wer");
+                break;
+            default:
+                throw new IllegalArgumentException("invalid dataset number: " + number);
+        }
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure all datasets are shown.
+        final UiObject2 picker = mUiBot.assertDatasets("Mr Plow", "El Barto", "Mr Sparkle");
+
+        // Auto-fill it.
+        mUiBot.selectDataset(picker, name);
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    /**
+     * Tests the scenario where the service uses custom remote views for different fields (username
+     * and password).
+     */
+    @Test
+    public void testAutofillOneDatasetCustomPresentation() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude",
+                        createPresentation("The Dude"))
+                .setField(ID_PASSWORD, "sweet",
+                        createPresentation("Dude's password"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Check initial field.
+        mUiBot.assertDatasets("The Dude");
+
+        // Then move around...
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Dude's password");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("The Dude");
+
+        // Auto-fill it.
+        requestFocusOnPassword();
+        mUiBot.selectDataset("Dude's password");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    /**
+     * Tests the scenario where the service uses custom remote views for different fields (username
+     * and password) and the dataset itself, and each dataset has the same number of fields.
+     */
+    @Test
+    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
+    public void testAutofillMultipleDatasetsCustomPresentations() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder(createPresentation("Dataset1"))
+                        .setField(ID_USERNAME, "user1") // no presentation
+                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
+                        .setField(ID_PASSWORD, "pass2") // no presentation
+                        .setPresentation(createPresentation("Dataset2"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("user1", "pass1");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Check initial field.
+        mUiBot.assertDatasets("Dataset1", "User2");
+
+        // Then move around...
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass1", "Dataset2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("Dataset1", "User2");
+
+        // Auto-fill it.
+        requestFocusOnPassword();
+        mUiBot.selectDataset("Pass1");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    /**
+     * Tests the scenario where the service uses custom remote views for different fields (username
+     * and password), and each dataset has the same number of fields.
+     */
+    @Test
+    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
+    public void testAutofillMultipleDatasetsCustomPresentationSameFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
+                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
+                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("user1", "pass1");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Check initial field.
+        mUiBot.assertDatasets("User1", "User2");
+
+        // Then move around...
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass1", "Pass2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("User1", "User2");
+
+        // Auto-fill it.
+        requestFocusOnPassword();
+        mUiBot.selectDataset("Pass1");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    /**
+     * Tests the scenario where the service uses custom remote views for different fields (username
+     * and password), but each dataset has a different number of fields.
+     */
+    @Test
+    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
+    public void testAutofillMultipleDatasetsCustomPresentationFirstDatasetMissingSecondField()
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
+                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("user2", "pass2");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Check initial field.
+        mUiBot.assertDatasets("User1", "User2");
+
+        // Then move around...
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("User1", "User2");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("User2");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    /**
+     * Tests the scenario where the service uses custom remote views for different fields (username
+     * and password), but each dataset has a different number of fields.
+     */
+    @Test
+    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
+    public void testAutofillMultipleDatasetsCustomPresentationSecondDatasetMissingFirstField()
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
+                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("user1", "pass1");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Check initial field.
+        mUiBot.assertDatasets("User1");
+
+        // Then move around...
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass1", "Pass2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("User1");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("User1");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnly() throws Exception {
+        saveOnlyTest(false);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnlyTriggeredManually() throws Exception {
+        saveOnlyTest(false);
+    }
+
+    private void saveOnlyTest(boolean manually) throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger auto-fill.
+        if (manually) {
+            mActivity.forceAutofillOnUsername();
+        } else {
+            mActivity.onUsername(View::requestFocus);
+        }
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        sReplier.assertNoUnhandledSaveRequests();
+        assertThat(saveRequest.datasetIds).isNull();
+
+        // Assert value of expected fields - should not be sanitized.
+        try {
+            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+            assertTextAndValue(username, "malkovich");
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+            assertTextAndValue(password, "malkovich");
+        } catch (AssertionError | RuntimeException e) {
+            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    public void testSaveGoesAwayWhenTappingHomeButton() throws Exception {
+        saveGoesAway(DismissType.HOME_BUTTON);
+    }
+
+    @Test
+    public void testSaveGoesAwayWhenTappingBackButton() throws Exception {
+        saveGoesAway(DismissType.BACK_BUTTON);
+    }
+
+    @Test
+    public void testSaveGoesAwayWhenTouchingOutside() throws Exception {
+        saveGoesAway(DismissType.TOUCH_OUTSIDE);
+    }
+
+    private void saveGoesAway(DismissType dismissType) throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Then make sure it goes away when user doesn't want it..
+        switch (dismissType) {
+            case BACK_BUTTON:
+                mUiBot.pressBack();
+                break;
+            case HOME_BUTTON:
+                mUiBot.pressHome();
+                break;
+            case TOUCH_OUTSIDE:
+                mUiBot.assertShownByText(expectedMessage).click();
+                break;
+            default:
+                throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
+        }
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnlyPreFilled() throws Exception {
+        saveOnlyTestPreFilled(false);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnlyTriggeredManuallyPreFilled() throws Exception {
+        saveOnlyTestPreFilled(true);
+    }
+
+    private void saveOnlyTestPreFilled(boolean manually) throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Set activity
+        mActivity.onUsername((v) -> v.setText("user_before"));
+        mActivity.onPassword((v) -> v.setText("pass_before"));
+
+        // Trigger auto-fill.
+        if (manually) {
+            // setText() will trigger a fill request.
+            // Waits the first fill request triggered by the setText() is received by the service to
+            // avoid flaky.
+            sReplier.getNextFillRequest();
+            mUiBot.waitForIdle();
+
+            // Set expectations again.
+            sReplier.addResponse(new CannedFillResponse.Builder()
+                    .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                    .build());
+            mActivity.forceAutofillOnUsername();
+        } else {
+            mUiBot.selectByRelativeId(ID_USERNAME);
+        }
+        mUiBot.waitForIdle();
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("user_after"));
+        mActivity.onPassword((v) -> v.setText("pass_after"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("user_after");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.waitForIdle();
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        sReplier.assertNoUnhandledSaveRequests();
+
+        // Assert value of expected fields - should not be sanitized.
+        try {
+            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+            assertTextAndValue(username, "user_after");
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+            assertTextAndValue(password, "pass_after");
+        } catch (AssertionError | RuntimeException e) {
+            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnlyTwoRequiredFieldsOnePrefilled() throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Set activity
+        mActivity.onUsername((v) -> v.setText("I_AM_USER"));
+
+        // Trigger auto-fill.
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before changing value, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Set credentials...
+        mActivity.onPassword((v) -> v.setText("thou should pass")); // contains pass
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("I_AM_USER"); // contains pass
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        sReplier.assertNoUnhandledSaveRequests();
+
+        // Assert value of expected fields - should not be sanitized.
+        try {
+            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+            assertTextAndValue(username, "I_AM_USER");
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+            assertTextAndValue(password, "thou should pass");
+        } catch (AssertionError | RuntimeException e) {
+            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnlyOptionalField() throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME)
+                .setOptionalSavableIds(ID_PASSWORD)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword(View::requestFocus);
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(username, "malkovich");
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+        assertTextAndValue(password, "malkovich");
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveNoRequiredField_NoneFilled() throws Exception {
+        optionalOnlyTest(FilledFields.NONE);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveNoRequiredField_OneFilled() throws Exception {
+        optionalOnlyTest(FilledFields.USERNAME_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveNoRequiredField_BothFilled() throws Exception {
+        optionalOnlyTest(FilledFields.BOTH);
+    }
+
+    enum FilledFields {
+        NONE,
+        USERNAME_ONLY,
+        BOTH
+    }
+
+    private void optionalOnlyTest(FilledFields filledFields) throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD)
+                .setOptionalSavableIds(ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        final String expectedUsername;
+        if (filledFields == FilledFields.USERNAME_ONLY || filledFields == FilledFields.BOTH) {
+            expectedUsername = BACKDOOR_USERNAME;
+            mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+        } else {
+            expectedUsername = "";
+        }
+        mActivity.onPassword(View::requestFocus);
+        if (filledFields == FilledFields.BOTH) {
+            mActivity.onPassword((v) -> v.setText("whatever"));
+        }
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage(expectedUsername);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        if (filledFields == FilledFields.NONE) {
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+            return;
+        }
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(username, BACKDOOR_USERNAME);
+
+        if (filledFields == FilledFields.BOTH) {
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+            assertTextAndValue(password, "whatever");
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testGenericSave() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSavePassword() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveAddress() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_ADDRESS);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveCreditCard() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveUsername() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_USERNAME);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveEmailAddress() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_EMAIL_ADDRESS);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveDebitCard() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_DEBIT_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSavePaymentCard() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_PAYMENT_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveGenericCard() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_GENERIC_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveTwoCardTypes() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD,
+                SAVE_DATA_TYPE_GENERIC_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveThreeCardTypes() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD
+                | SAVE_DATA_TYPE_PAYMENT_CARD, SAVE_DATA_TYPE_GENERIC_CARD);
+    }
+
+    private void customizedSaveTest(int type) throws Exception {
+        customizedSaveTest(type, type);
+    }
+
+    private void customizedSaveTest(int type, int expectedType) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final String saveDescription = "Your data will be saved with love and care...";
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(type, ID_USERNAME, ID_PASSWORD)
+                .setSaveDescription(saveDescription)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started.
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        final UiObject2 saveSnackBar = mUiBot.assertSaveShowing(saveDescription, expectedType);
+        mUiBot.saveForAutofill(saveSnackBar, true);
+
+        // Assert save was called.
+        sReplier.getNextSaveRequest();
+    }
+
+    @Test
+    public void testDontTriggerSaveOnFinishWhenRequestedByFlag() throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Make sure it didn't trigger save.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    public void testAutoFillOneDatasetAndSaveWhenFlagSecure() throws Exception {
+        mActivity.setFlags(FLAG_SECURE);
+        testAutoFillOneDatasetAndSave();
+    }
+
+    @Test
+    public void testAutoFillOneDatasetWhenFlagSecure() throws Exception {
+        mActivity.setFlags(FLAG_SECURE);
+        testAutoFillOneDataset();
+    }
+
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testDisableSelf() throws Exception {
+        enableService();
+
+        // Can disable while connected.
+        mActivity.runOnUiThread(() -> mContext.getSystemService(
+                AutofillManager.class).disableAutofillServices());
+
+        // Ensure disabled.
+        assertServiceDisabled();
+    }
+
+    @Test
+    public void testNeverRejectStyleNegativeSaveButton() throws Exception {
+        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_NEVER);
+    }
+
+    @Test
+    public void testRejectStyleNegativeSaveButton() throws Exception {
+        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT);
+    }
+
+    @Test
+    public void testCancelStyleNegativeSaveButton() throws Exception {
+        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL);
+    }
+
+    private void negativeSaveButtonStyle(int style) throws Exception {
+        enableService();
+
+        // Set service behavior.
+
+        final String intentAction = "android.autofillservice.cts.CUSTOM_ACTION";
+
+        // Configure the save UI.
+        final IntentSender listener = PendingIntent.getBroadcast(
+                mContext, 0, new Intent(intentAction), 0).getIntentSender();
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setNegativeAction(style, listener)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("foo"));
+        mActivity.onPassword((v) -> v.setText("foo"));
+        mActivity.tapLogin();
+
+        // Start watching for the negative intent
+        final CountDownLatch latch = new CountDownLatch(1);
+        final IntentFilter intentFilter = new IntentFilter(intentAction);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mContext.unregisterReceiver(this);
+                latch.countDown();
+            }
+        }, intentFilter);
+
+        // Trigger the negative button.
+        mUiBot.saveForAutofill(style, /* yesDoIt= */ false, SAVE_DATA_TYPE_PASSWORD);
+
+        // Wait for the custom action.
+        assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
+    }
+
+    @Test
+    public void testContinueStylePositiveSaveButton() throws Exception {
+        enableService();
+
+        // Set service behavior.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setPositiveAction(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("foo"));
+        mActivity.onPassword((v) -> v.setText("foo"));
+        mActivity.tapLogin();
+
+        // Start watching for the negative intent
+        // Trigger the negative button.
+        mUiBot.saveForAutofill(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert save was called.
+        sReplier.getNextSaveRequest();
+    }
+
+    @Test
+    @AppModeFull(reason = "Unit test")
+    public void testGetTextInputType() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Assert input text on fill request:
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        final ViewNode label = findNodeByResourceId(fillRequest.structure, ID_PASSWORD_LABEL);
+        assertThat(label.getInputType()).isEqualTo(TYPE_NULL);
+        final ViewNode password = findNodeByResourceId(fillRequest.structure, ID_PASSWORD);
+        assertWithMessage("No TYPE_TEXT_VARIATION_PASSWORD on %s", password.getInputType())
+                .that(password.getInputType() & TYPE_TEXT_VARIATION_PASSWORD)
+                .isEqualTo(TYPE_TEXT_VARIATION_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "Unit test")
+    public void testNoContainers() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        mUiBot.assertNoDatasetsEver();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert it only has 1 root view with 10 "leaf" nodes:
+        // 1.text view for app title
+        // 2.username text label
+        // 3.username text field
+        // 4.password text label
+        // 5.password text field
+        // 6.output text field
+        // 7.clear button
+        // 8.save button
+        // 9.login button
+        // 10.cancel button
+        //
+        // But it also has an intermediate container (for username) that should be included because
+        // it has a resource id.
+
+        assertNumberOfChildren(fillRequest.structure, 12);
+
+        // Make sure container with a resource id was included:
+        final ViewNode usernameContainer = findNodeByResourceId(fillRequest.structure,
+                ID_USERNAME_CONTAINER);
+        assertThat(usernameContainer).isNotNull();
+        assertThat(usernameContainer.getChildCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void testAutofillManuallyOneDataset() throws Exception {
+        // Set service.
+        enableService();
+
+        // And activity.
+        mActivity.onUsername((v) -> v.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO));
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Explicitly uses the contextual menu to test that functionality.
+        mUiBot.getAutofillMenuOption(ID_USERNAME).click();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
+
+        // Should have been automatically filled.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillManuallyOneDatasetWhenClipboardFull() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set clipboard.
+        ClipboardManager cm = (ClipboardManager) mActivity.getSystemService(CLIPBOARD_SERVICE);
+        cm.setPrimaryClip(ClipData.newPlainText(null, "test"));
+
+        // And activity.
+        mActivity.onUsername((v) -> v.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO));
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Explicitly uses the contextual menu to test that functionality.
+        mUiBot.getAutofillMenuOption(ID_USERNAME).click();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
+
+        // Should have been automatically filled.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // clear clipboard
+        cm.clearPrimaryClip();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception {
+        autofillManuallyTwoDatasets(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception {
+        autofillManuallyTwoDatasets(false);
+    }
+
+    private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "jenny")
+                        .setField(ID_PASSWORD, "8675309")
+                        .setPresentation(createPresentation("Jenny"))
+                        .build())
+                .build());
+        if (pickFirst) {
+            mActivity.expectAutoFill("dude", "sweet");
+        } else {
+            mActivity.expectAutoFill("jenny", "8675309");
+
+        }
+
+        // Force a manual autofill request.
+        mActivity.forceAutofillOnUsername();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
+
+        // Auto-fill it.
+        final UiObject2 picker = mUiBot.assertDatasets("The Dude", "Jenny");
+        mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyPartialField() throws Exception {
+        // Set service.
+        enableService();
+
+        sReplier.addResponse(NO_RESPONSE);
+        // And activity.
+        mActivity.onUsername((v) -> v.setText("dud"));
+        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
+
+        // setText() will trigger a fill request.
+        // Waits the first fill request triggered by the setText() is received by the service to
+        // avoid flaky.
+        sReplier.getNextFillRequest();
+        mUiBot.waitForIdle();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Force a manual autofill request.
+        mActivity.forceAutofillOnUsername();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
+        // Username value should be available because it triggered the manual request...
+        assertValue(fillRequest.structure, ID_USERNAME, "dud");
+        // ... but password didn't
+        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+
+        // Selects the dataset.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyAgainAfterAutomaticallyAutofilledBefore() throws Exception {
+        // Set service.
+        enableService();
+
+        /*
+         * 1st fill (automatic).
+         */
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Assert request.
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+        assertTextIsSanitized(fillRequest1.structure, ID_USERNAME);
+        assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
+
+        // Select it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        /*
+         * 2nd fill (manual).
+         */
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "DUDE")
+                .setField(ID_PASSWORD, "SWEET")
+                .setPresentation(createPresentation("THE DUDE"))
+                .build());
+        mActivity.expectAutoFill("DUDE", "SWEET");
+        // Change password to make sure it's not sent to the service.
+        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
+
+        // Trigger auto-fill.
+        mActivity.forceAutofillOnUsername();
+
+        // Assert request.
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
+        assertValue(fillRequest2.structure, ID_USERNAME, "dude");
+        assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
+
+        // Select it.
+        mUiBot.selectDataset("THE DUDE");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyAgainAfterManuallyAutofilledBefore() throws Exception {
+        // Set service.
+        enableService();
+
+        /*
+         * 1st fill (manual).
+         */
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        mActivity.forceAutofillOnUsername();
+
+        // Assert request.
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest1.flags, FLAG_MANUAL_REQUEST);
+        assertValue(fillRequest1.structure, ID_USERNAME, "");
+        assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
+
+        // Select it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        /*
+         * 2nd fill (manual).
+         */
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "DUDE")
+                .setField(ID_PASSWORD, "SWEET")
+                .setPresentation(createPresentation("THE DUDE"))
+                .build());
+        mActivity.expectAutoFill("DUDE", "SWEET");
+        // Change password to make sure it's not sent to the service.
+        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
+
+        // Trigger auto-fill.
+        mActivity.forceAutofillOnUsername();
+
+        // Assert request.
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
+        assertValue(fillRequest2.structure, ID_USERNAME, "dude");
+        assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
+
+        // Select it.
+        mUiBot.selectDataset("THE DUDE");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testCommitMultipleTimes() throws Throwable {
+        // Set service.
+        enableService();
+
+        final CannedFillResponse response = new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build();
+
+        for (int i = 1; i <= 10; i++) {
+            Log.i(TAG, "testCommitMultipleTimes(): step " + i);
+            final String username = "user-" + i;
+            final String password = "pass-" + i;
+            try {
+                // Set expectations.
+                sReplier.addResponse(response);
+
+                Timeouts.IDLE_UNBIND_TIMEOUT.run("wait for session created", () -> {
+                    // Trigger auto-fill.
+                    mActivity.onUsername(View::clearFocus);
+                    mActivity.onUsername(View::requestFocus);
+
+                    return isConnected() ? "not_used" : null;
+                });
+
+                sReplier.getNextFillRequest();
+
+                // Validation check.
+                mUiBot.assertNoDatasetsEver();
+
+                // Set credentials...
+                mActivity.onUsername((v) -> v.setText(username));
+                mActivity.onPassword((v) -> v.setText(password));
+
+                // Change focus to prepare for next step - must do it before session is gone
+                mActivity.onPassword(View::requestFocus);
+
+                // ...and save them
+                mActivity.tapSave();
+
+                // Assert the snack bar is shown and tap "Save".
+                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+                // Assert value of expected fields - should not be sanitized.
+                final ViewNode usernameNode = findNodeByResourceId(saveRequest.structure,
+                        ID_USERNAME);
+                assertTextAndValue(usernameNode, username);
+                final ViewNode passwordNode = findNodeByResourceId(saveRequest.structure,
+                        ID_PASSWORD);
+                assertTextAndValue(passwordNode, password);
+
+                waitUntilDisconnected();
+
+                // Wait and check if the save window is correctly hidden.
+                mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+            } catch (RetryableException e) {
+                throw new RetryableException(e, "on step %d", i);
+            } catch (Throwable t) {
+                throw new Throwable("Error on step " + i, t);
+            }
+        }
+    }
+
+    @Test
+    public void testCancelMultipleTimes() throws Throwable {
+        // Set service.
+        enableService();
+
+        for (int i = 1; i <= 10; i++) {
+            Log.i(TAG, "testCancelMultipleTimes(): step " + i);
+            final String username = "user-" + i;
+            final String password = "pass-" + i;
+            sReplier.addResponse(new CannedDataset.Builder()
+                    .setField(ID_USERNAME, username)
+                    .setField(ID_PASSWORD, password)
+                    .setPresentation(createPresentation("The Dude"))
+                    .build());
+            mActivity.expectAutoFill(username, password);
+            try {
+                // Trigger auto-fill.
+                requestFocusOnUsername();
+
+                waitUntilConnected();
+                sReplier.getNextFillRequest();
+
+                // Auto-fill it.
+                mUiBot.selectDataset("The Dude");
+
+                // Check the results.
+                mActivity.assertAutoFilled();
+
+                // Change focus to prepare for next step - must do it before session is gone
+                requestFocusOnPassword();
+
+                // Rinse and repeat...
+                mActivity.tapClear();
+
+                waitUntilDisconnected();
+            } catch (RetryableException e) {
+                throw e;
+            } catch (Throwable t) {
+                throw new Throwable("Error on step " + i, t);
+            }
+        }
+    }
+
+    @Test
+    public void testClickCustomButton() throws Exception {
+        // Set service.
+        enableService();
+
+        Intent intent = new Intent(mContext, EmptyActivity.class);
+        IntentSender sender = PendingIntent.getActivity(mContext, 0, intent,
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT)
+                .getIntentSender();
+
+        RemoteViews presentation = new RemoteViews(mPackageName, R.layout.list_item);
+        presentation.setTextViewText(R.id.text1, "Poke");
+        Intent firstIntent = new Intent(mContext, DummyActivity.class);
+        presentation.setOnClickPendingIntent(R.id.text1, PendingIntent.getActivity(
+                mContext, 0, firstIntent, PendingIntent.FLAG_ONE_SHOT
+                        | PendingIntent.FLAG_CANCEL_CURRENT));
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(sender, ID_USERNAME)
+                .setPresentation(presentation)
+                .build());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Click on the custom button
+        mUiBot.selectByText("Poke");
+
+        // Make sure the click worked
+        mUiBot.selectByText("foo");
+
+        // Go back to the filled app.
+        mUiBot.pressBack();
+    }
+
+    @Test
+    public void testIsServiceEnabled() throws Exception {
+        disableService();
+        final AutofillManager afm = mActivity.getAutofillManager();
+        assertThat(afm.hasEnabledAutofillServices()).isFalse();
+        try {
+            enableService();
+            assertThat(afm.hasEnabledAutofillServices()).isTrue();
+        } finally {
+            disableService();
+        }
+    }
+
+    @Test
+    public void testGetAutofillServiceComponentName() throws Exception {
+        final AutofillManager afm = mActivity.getAutofillManager();
+
+        enableService();
+        final ComponentName componentName = afm.getAutofillServiceComponentName();
+        assertThat(componentName.getPackageName()).isEqualTo(SERVICE_PACKAGE);
+        assertThat(componentName.getClassName()).endsWith(SERVICE_CLASS);
+
+        disableService();
+        assertThat(afm.getAutofillServiceComponentName()).isNull();
+    }
+
+    @Test
+    public void testSetupComplete() throws Exception {
+        enableService();
+
+        // Validation check.
+        final AutofillManager afm = mActivity.getAutofillManager();
+        Helper.assertAutofillEnabled(afm, true);
+
+        // Now disable user_complete and try again.
+        try {
+            setUserComplete(mContext, false);
+            Helper.assertAutofillEnabled(afm, false);
+        } finally {
+            setUserComplete(mContext, true);
+        }
+    }
+
+    @Test
+    public void testPopupGoesAwayWhenServiceIsChanged() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Now disable service by setting another service
+        Helper.enableAutofillService(mContext, NoOpAutofillService.SERVICE_NAME);
+
+        // ...and make sure popup's gone
+        mUiBot.assertNoDatasets();
+    }
+
+    // TODO(b/70682223): add a new test to make sure service with BIND_AUTOFILL permission works
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testServiceIsDisabledWhenNewServiceInfoIsInvalid() throws Exception {
+        serviceIsDisabledWhenNewServiceIsInvalid(BadAutofillService.SERVICE_NAME);
+    }
+
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testServiceIsDisabledWhenNewServiceNameIsInvalid() throws Exception {
+        serviceIsDisabledWhenNewServiceIsInvalid("Y_U_NO_VALID");
+    }
+
+    private void serviceIsDisabledWhenNewServiceIsInvalid(String serviceName) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Now disable service by setting another service...
+        Helper.enableAutofillService(mContext, serviceName);
+
+        // ...and make sure popup's gone
+        mUiBot.assertNoDatasets();
+
+        // Then try to trigger autofill again...
+        mActivity.onPassword(View::requestFocus);
+        //...it should not work!
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    public void testAutofillMovesCursorToTheEnd() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // NOTE: need to call getSelectionEnd() inside the UI thread, otherwise it returns 0
+        final AtomicInteger atomicBombToKillASmallInsect = new AtomicInteger();
+
+        mActivity.onUsername((v) -> atomicBombToKillASmallInsect.set(v.getSelectionEnd()));
+        assertWithMessage("Wrong position on username").that(atomicBombToKillASmallInsect.get())
+                .isEqualTo(4);
+
+        mActivity.onPassword((v) -> atomicBombToKillASmallInsect.set(v.getSelectionEnd()));
+        assertWithMessage("Wrong position on password").that(atomicBombToKillASmallInsect.get())
+                .isEqualTo(5);
+    }
+
+    @Test
+    public void testAutofillLargeNumberOfDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        final StringBuilder bigStringBuilder = new StringBuilder();
+        for (int i = 0; i < 10_000; i++) {
+            bigStringBuilder.append("BigAmI");
+        }
+        final String bigString = bigStringBuilder.toString();
+
+        final int size = 100;
+        Log.d(TAG, "testAutofillLargeNumberOfDatasets(): " + size + " datasets with "
+                + bigString.length() + "-bytes id");
+
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder();
+        for (int i = 0; i < size; i++) {
+            final String suffix = "-" + (i + 1);
+            response.addDataset(new CannedDataset.Builder()
+                    .setField(ID_USERNAME, "user" + suffix)
+                    .setField(ID_PASSWORD, "pass" + suffix)
+                    .setId(bigString)
+                    .setPresentation(createPresentation("DS" + suffix))
+                    .build());
+        }
+
+        // Set expectations.
+        sReplier.addResponse(response.build());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure all datasets are shown.
+        // TODO: improve assertDatasets() so it supports scrolling, and assert all of them are
+        // shown. In fullscreen there are 4 items, otherwise there are 3 items.
+        mUiBot.assertDatasetsContains("DS-1", "DS-2", "DS-3");
+
+        // TODO: once it supports scrolling, selects the last dataset and asserts it's filled.
+    }
+
+    @Test
+    public void testCancellationSignalCalledAfterTimeout() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final OneTimeCancellationSignalListener listener =
+                new OneTimeCancellationSignalListener(Timeouts.FILL_TIMEOUT.ms() + 2000);
+        sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Attach listener to CancellationSignal.
+        waitUntilConnected();
+        sReplier.getNextFillRequest().cancellationSignal.setOnCancelListener(listener);
+
+        // Assert results
+        listener.assertOnCancelCalled();
+    }
+
+    @Test
+    @AppModeFull(reason = "Unit test")
+    public void testNewTextAttributes() throws Exception {
+        enableService();
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.onUsername(View::requestFocus);
+
+        final FillRequest request = sReplier.getNextFillRequest();
+        final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
+        assertThat(username.getMinTextEms()).isEqualTo(2);
+        assertThat(username.getMaxTextEms()).isEqualTo(5);
+        assertThat(username.getMaxTextLength()).isEqualTo(25);
+
+        final ViewNode container = findNodeByResourceId(request.structure, ID_USERNAME_CONTAINER);
+        assertThat(container.getMinTextEms()).isEqualTo(-1);
+        assertThat(container.getMaxTextEms()).isEqualTo(-1);
+        assertThat(container.getMaxTextLength()).isEqualTo(-1);
+
+        final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
+        assertThat(password.getMinTextEms()).isEqualTo(-1);
+        assertThat(password.getMaxTextEms()).isEqualTo(-1);
+        assertThat(password.getMaxTextLength()).isEqualTo(5000);
+    }
+
+    @Test
+    public void testUiShowOnChangeAfterAutofill() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude", createPresentation("dude"))
+                .setField(ID_PASSWORD, "sweet", createPresentation("sweet"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("dude");
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+
+        // Delete a character.
+        sendKeyEvent("KEYCODE_DEL");
+        assertThat(mUiBot.getTextByRelativeId(ID_USERNAME)).isEqualTo("dud");
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Check autofill UI show.
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dude");
+
+        // Autofill again.
+        mUiBot.selectDataset(datasetPicker, "dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testUiShowOnChangeAfterAutofillOnePresentation() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("The Dude");
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+
+        // Delete username
+        mUiBot.setTextByRelativeId(ID_USERNAME, "");
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Check autofill UI show.
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
+
+        // Autofill again.
+        mUiBot.selectDataset(datasetPicker, "The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testCancelActionButton() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentationWithCancel("The Dude"))
+                        .build())
+                .setPresentationCancelIds(new int[]{R.id.cancel_fill});
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasetsContains("The Dude");
+
+        // Tap cancel button on fill UI
+        mUiBot.selectByRelativeId(ID_CANCEL_FILL);
+        mUiBot.waitForIdle();
+
+        mUiBot.assertNoDatasets();
+
+        // Test and verify auto-fill does not trigger
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.waitForIdle();
+
+        mUiBot.assertNoDatasetsEver();
+
+        // Test and verify auto-fill does not trigger.
+        mActivity.onUsername(View::requestFocus);
+        mUiBot.waitForIdle();
+
+        mUiBot.assertNoDatasetsEver();
+
+        // Reset
+        mActivity.tapClear();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentationWithCancel("The Dude"))
+                        .build())
+                .setPresentationCancelIds(new int[]{R.id.cancel});
+        sReplier.addResponse(builder2.build());
+
+        // Trigger auto-fill.
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Verify auto-fill has been triggered.
+        mUiBot.assertDatasetsContains("The Dude");
+    }
+
+    @Test
+    @AppModeFull(reason = "WRITE_SECURE_SETTING permission can't be grant to instant apps")
+    public void testSwitchInputMethod_noNewFillRequest() throws Exception {
+        // Set service
+        enableService();
+
+        // Set expectations
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build());
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasetsContains("The Dude");
+
+        // Trigger IME switch event
+        Helper.mockSwitchInputMethod(sContext);
+        mUiBot.waitForIdleSync();
+
+        // Tap password field
+        mUiBot.selectByRelativeId(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertDatasetsContains("The Dude");
+
+        // No new fill request
+        sReplier.assertNoUnhandledFillRequests();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginWithStringsActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginWithStringsActivityTest.java
new file mode 100644
index 0000000..f4f532b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginWithStringsActivityTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.LoginActivity.AUTHENTICATION_MESSAGE;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertHintFromResources;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextFromResources;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilConnected;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.LoginWithStringsActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.view.View;
+
+import org.junit.Test;
+
+@AppModeFull(reason = "LoginActivityTest is enough")
+public class LoginWithStringsActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<LoginWithStringsActivity> {
+
+    private LoginWithStringsActivity mActivity;
+
+
+    @Override
+    protected AutofillActivityTestRule<LoginWithStringsActivity> getActivityRule() {
+        return new AutofillActivityTestRule<LoginWithStringsActivity>(
+                LoginWithStringsActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testAutoFillOneDatasetAndSave() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Bundle extras = new Bundle();
+        extras.putString("numbers", "4815162342");
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setId("I'm the alpha and the omega")
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setExtras(extras)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+        waitUntilConnected();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Make sure input was sanitized.
+        assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+
+        // Make sure labels were not sanitized
+        assertTextFromResources(fillRequest.structure, ID_USERNAME_LABEL, "Username", false,
+                "username_string");
+        assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+                "password_string");
+
+        // Check text hints
+        assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
+                "username_hint");
+        assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
+                "password_hint");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Try to login, it will fail.
+        final String loginMessage = mActivity.tapLogin();
+
+        assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(AUTHENTICATION_MESSAGE);
+
+        // Set right password...
+        mActivity.onPassword((v) -> v.setText("dude"));
+
+        // ... and try again
+        final String expectedMessage = getWelcomeMessage("dude");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(username, "dude");
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(password, "dude");
+
+        // Make sure labels were not sanitized
+        assertTextFromResources(saveRequest.structure, ID_USERNAME_LABEL, "Username", false,
+                "username_string");
+        assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+                "password_string");
+
+        // Check text hints
+        assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
+                "username_hint");
+        assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
+                "password_hint");
+
+        // Make sure extras were passed back on onSave()
+        assertThat(saveRequest.data).isNotNull();
+        final String extraValue = saveRequest.data.getString("numbers");
+        assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/MultipleFragmentLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/MultipleFragmentLoginTest.java
new file mode 100644
index 0000000..b5e5ec8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/MultipleFragmentLoginTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.FragmentContainerActivity.FRAGMENT_TAG;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.FragmentContainerActivity;
+import android.autofillservice.cts.activities.FragmentWithMoreEditTexts;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillValue;
+import android.widget.EditText;
+
+import org.junit.Test;
+
+public class MultipleFragmentLoginTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<FragmentContainerActivity> {
+
+    private static final String LOG_TAG = "MultipleFragmentLoginTest";
+
+    private FragmentContainerActivity mActivity;
+    private EditText mEditText1;
+    private EditText mEditText2;
+
+    @Override
+    protected AutofillActivityTestRule<FragmentContainerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<FragmentContainerActivity>(
+                FragmentContainerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+                mEditText1 = mActivity.findViewById(R.id.editText1);
+                mEditText2 = mActivity.findViewById(R.id.editText2);
+            }
+        };
+    }
+
+    @Test
+    public void loginOnTwoFragments() throws Exception {
+        enableService();
+
+        Bundle clientState = new Bundle();
+        clientState.putString("key", "value1");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField("editText1", "editText1-autofilled")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .setExtras(clientState)
+                .build());
+
+        // Trigger autofill on editText2
+        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
+
+        final InstrumentedAutoFillService.FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.data).isNull();
+
+        mUiBot.assertNoDatasetsEver(); // UI is only shown on editText1
+
+        mActivity.setRootContainerFocusable(false);
+
+        final AssistStructure structure = fillRequest1.contexts.get(0).getStructure();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        assertThat(findNodeByResourceId(structure, "editText1")).isNotNull();
+        assertThat(findNodeByResourceId(structure, "editText2")).isNotNull();
+        assertThat(findNodeByResourceId(structure, "editText3")).isNull();
+        assertThat(findNodeByResourceId(structure, "editText4")).isNull();
+        assertThat(findNodeByResourceId(structure, "editText5")).isNull();
+
+        // Wait until autofill has been applied
+        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
+        mUiBot.selectDataset("dataset1");
+        mUiBot.assertShownByText("editText1-autofilled");
+
+        // Manually fill view
+        mActivity.syncRunOnUiThread(() -> mEditText2.setText("editText2-manually-filled"));
+
+        // Replacing the fragment focused a previously unknown view which triggers a new
+        // partition
+        clientState.putString("key", "value2");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField("editText3", "editText3-autofilled")
+                        .setField("editText4", "editText4-autofilled")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText2", "editText5")
+                .setExtras(clientState)
+                .build());
+
+        Log.i(LOG_TAG, "Switching Fragments");
+        mActivity.syncRunOnUiThread(
+                () -> mActivity.getFragmentManager().beginTransaction().replace(
+                        R.id.rootContainer, new FragmentWithMoreEditTexts(),
+                        FRAGMENT_TAG).commitNow());
+        EditText editText5 = mActivity.findViewById(R.id.editText5);
+        final InstrumentedAutoFillService.FillRequest fillRequest2 = sReplier.getNextFillRequest();
+
+        // The fillRequest should have a fillContext for each partition. The first partition
+        // should be filled in
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
+
+        assertThat(fillRequest2.data.getString("key")).isEqualTo("value1");
+
+        final AssistStructure structure1 = fillRequest2.contexts.get(0).getStructure();
+        ViewNode editText1Node = findNodeByResourceId(structure1, "editText1");
+        // The actual value in the structure is not updated in FillRequest-contexts, but the
+        // autofill value is. For text views in SaveRequest both are updated, but this is the
+        // only exception.
+        assertThat(editText1Node.getAutofillValue()).isEqualTo(
+                AutofillValue.forText("editText1-autofilled"));
+
+        ViewNode editText2Node = findNodeByResourceId(structure1, "editText2");
+        // Manually filled fields are not send to onFill. They appear in onSave if they are set
+        // as saveable fields.
+        assertThat(editText2Node.getText().toString()).isEqualTo("");
+
+        assertThat(findNodeByResourceId(structure1, "editText3")).isNull();
+        assertThat(findNodeByResourceId(structure1, "editText4")).isNull();
+        assertThat(findNodeByResourceId(structure1, "editText5")).isNull();
+
+        final AssistStructure structure2 = fillRequest2.contexts.get(1).getStructure();
+
+        assertThat(findNodeByResourceId(structure2, "editText1")).isNull();
+        assertThat(findNodeByResourceId(structure2, "editText2")).isNull();
+        assertThat(findNodeByResourceId(structure2, "editText3")).isNotNull();
+        assertThat(findNodeByResourceId(structure2, "editText4")).isNotNull();
+        assertThat(findNodeByResourceId(structure2, "editText5")).isNotNull();
+
+        // Wait until autofill has been applied
+        mUiBot.selectDataset("dataset2");
+        mUiBot.assertShownByText("editText3-autofilled");
+        mUiBot.assertShownByText("editText4-autofilled");
+
+        // Manually fill view
+        mActivity.syncRunOnUiThread(() -> editText5.setText("editText5-manually-filled"));
+
+        // Finish activity and save data
+        mActivity.finish();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // The saveRequest should have a fillContext for each partition with all the data
+        final InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        assertThat(saveRequest.data.getString("key")).isEqualTo("value2");
+
+        final AssistStructure saveStructure1 = saveRequest.contexts.get(0).getStructure();
+        editText1Node = findNodeByResourceId(saveStructure1, "editText1");
+        assertThat(editText1Node.getText().toString()).isEqualTo("editText1-autofilled");
+
+        editText2Node = findNodeByResourceId(saveStructure1, "editText2");
+        assertThat(editText2Node.getText().toString()).isEqualTo("editText2-manually-filled");
+
+        assertThat(findNodeByResourceId(saveStructure1, "editText3")).isNull();
+        assertThat(findNodeByResourceId(saveStructure1, "editText4")).isNull();
+        assertThat(findNodeByResourceId(saveStructure1, "editText5")).isNull();
+
+        final AssistStructure saveStructure2 = saveRequest.contexts.get(1).getStructure();
+        assertThat(findNodeByResourceId(saveStructure2, "editText1")).isNull();
+        assertThat(findNodeByResourceId(saveStructure2, "editText2")).isNull();
+
+        ViewNode editText3Node = findNodeByResourceId(saveStructure2, "editText3");
+        assertThat(editText3Node.getText().toString()).isEqualTo("editText3-autofilled");
+
+        ViewNode editText4Node = findNodeByResourceId(saveStructure2, "editText4");
+        assertThat(editText4Node.getText().toString()).isEqualTo("editText4-autofilled");
+
+        ViewNode editText5Node = findNodeByResourceId(saveStructure2, "editText5");
+        assertThat(editText5Node.getText().toString()).isEqualTo("editText5-manually-filled");
+    }
+
+    @Test
+    public void uiDismissedWhenNonSavableFragmentIsGone() throws Exception {
+        uiDismissedWhenFragmentIsGoneText(false);
+    }
+
+    @Test
+    public void uiDismissedWhenSavableFragmentIsGone() throws Exception {
+        uiDismissedWhenFragmentIsGoneText(true);
+    }
+
+    private void uiDismissedWhenFragmentIsGoneText(boolean savable) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField("editText1", "whatever")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build());
+        if (savable) {
+            response.setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText2");
+        }
+
+        sReplier.addResponse(response.build());
+
+        // Trigger autofill on editText2
+        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver(); // UI is only shown on editText1
+
+        mActivity.setRootContainerFocusable(false);
+
+        // Check UI is shown, but don't select it.
+        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
+        mUiBot.assertDatasets("dataset1");
+
+        // Switch fragments
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.syncRunOnUiThread(
+                () -> mActivity.getFragmentManager().beginTransaction().replace(
+                        R.id.rootContainer, new FragmentWithMoreEditTexts(),
+                        FRAGMENT_TAG).commitNow());
+        // Make sure UI is gone.
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasets();
+    }
+
+    // TODO: add similar tests for fragment with virtual view
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/PartitionedActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/PartitionedActivityTest.java
new file mode 100644
index 0000000..5408413
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/PartitionedActivityTest.java
@@ -0,0 +1,2282 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C2;
+import static android.autofillservice.cts.activities.GridActivity.ID_L2C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L2C2;
+import static android.autofillservice.cts.activities.GridActivity.ID_L3C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L3C2;
+import static android.autofillservice.cts.activities.GridActivity.ID_L4C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L4C2;
+import static android.autofillservice.cts.testcore.Helper.UNUSED_AUTOFILL_VALUE;
+import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertValue;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.Helper.getMaxPartitions;
+import static android.autofillservice.cts.testcore.Helper.setMaxPartitions;
+import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.activities.GridActivity.FillExpectation;
+import android.autofillservice.cts.commontests.AbstractGridActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.FillResponse;
+
+import org.junit.Test;
+
+/**
+ * Test case for an activity containing multiple partitions.
+ */
+@AppModeFull(reason = "Service-specific test")
+public class PartitionedActivityTest extends AbstractGridActivityTestCase {
+
+    @Test
+    public void testAutofillTwoPartitionsSkipFirst() throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+
+        // Trigger auto-fill on 1st partition.
+        focusCell(1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+        final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        final ViewNode p1l1c2 = assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
+        assertWithMessage("Focus on p1l1c1").that(p1l1c1.isFocused()).isTrue();
+        assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
+
+        // Make sure UI is shown, but don't tap it.
+        mUiBot.assertDatasets("l1c1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("l1c2");
+
+        // Now tap a field in a different partition
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+
+        // Trigger auto-fill on 2nd partition.
+        focusCell(2, 1);
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.flags).isEqualTo(0);
+        final ViewNode p2l1c1 = assertTextIsSanitized(fillRequest2.structure, ID_L1C1);
+        final ViewNode p2l1c2 = assertTextIsSanitized(fillRequest2.structure, ID_L1C2);
+        final ViewNode p2l2c1 = assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
+        final ViewNode p2l2c2 = assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
+        assertWithMessage("Focus on p2l1c1").that(p2l1c1.isFocused()).isFalse();
+        assertWithMessage("Focus on p2l1c2").that(p2l1c2.isFocused()).isFalse();
+        assertWithMessage("Focus on p2l2c1").that(p2l2c1.isFocused()).isTrue();
+        assertWithMessage("Focus on p2l2c2").that(p2l2c2.isFocused()).isFalse();
+        // Make sure UI is shown, but don't tap it.
+        mUiBot.assertDatasets("l2c1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("l2c2");
+
+        // Now fill them
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1").onCell(1, 2, "l1c2");
+        focusCell(1, 1);
+        mUiBot.selectDataset("l1c1");
+        expectation1.assertAutoFilled();
+
+        // Change previous values to make sure they are not filled again
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.setText(1, 2, "L1C2");
+
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 1, "l2c1").onCell(2, 2, "l2c2");
+        focusCell(2, 2);
+        mUiBot.selectDataset("l2c2");
+        expectation2.assertAutoFilled();
+
+        // Make sure previous partition didn't change
+        assertThat(mActivity.getText(1, 1)).isEqualTo("L1C1");
+        assertThat(mActivity.getText(1, 2)).isEqualTo("L1C2");
+    }
+
+    @Test
+    public void testAutofillTwoPartitionsInSequence() throws Exception {
+        // Set service.
+        enableService();
+
+        // 1st partition
+        // Prepare.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 1"))
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger auto-fill.
+        focusCell(1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+
+        assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 1");
+
+        // Check the results.
+        expectation1.assertAutoFilled();
+
+        // 2nd partition
+        // Prepare.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 2"))
+                        .setField(ID_L2C1, "l2c1")
+                        .setField(ID_L2C2, "l2c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 1, "l2c1")
+                .onCell(2, 2, "l2c2");
+
+        // Trigger auto-fill.
+        focusCell(2, 1);
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.flags).isEqualTo(0);
+
+        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
+        assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
+        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 2");
+
+        // Check the results.
+        expectation2.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofill4PartitionsAutomatically() throws Exception {
+        autofill4PartitionsTest(false);
+    }
+
+    @Test
+    public void testAutofill4PartitionsManually() throws Exception {
+        autofill4PartitionsTest(true);
+    }
+
+    private void autofill4PartitionsTest(boolean manually) throws Exception {
+        // Set service.
+        enableService();
+
+        // 1st partition
+        // Prepare.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 1"))
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger auto-fill.
+        mActivity.triggerAutofill(manually, 1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+
+        if (manually) {
+            assertHasFlags(fillRequest1.flags, FLAG_MANUAL_REQUEST);
+            assertValue(fillRequest1.structure, ID_L1C1, "");
+        } else {
+            assertThat(fillRequest1.flags).isEqualTo(0);
+            assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        }
+        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 1");
+
+        // Check the results.
+        expectation1.assertAutoFilled();
+
+        // 2nd partition
+        // Prepare.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 2"))
+                        .setField(ID_L2C1, "l2c1")
+                        .setField(ID_L2C2, "l2c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 1, "l2c1")
+                .onCell(2, 2, "l2c2");
+
+        // Trigger auto-fill.
+        mActivity.triggerAutofill(manually, 2, 1);
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+
+        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
+        if (manually) {
+            assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
+            assertValue(fillRequest2.structure, ID_L2C1, "");
+        } else {
+            assertThat(fillRequest2.flags).isEqualTo(0);
+            assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
+        }
+        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 2");
+
+        // Check the results.
+        expectation2.assertAutoFilled();
+
+        // 3rd partition
+        // Prepare.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 3"))
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+
+        // Trigger auto-fill.
+        mActivity.triggerAutofill(manually, 3, 1);
+        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
+
+        assertValue(fillRequest3.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest3.structure, ID_L1C2, "l1c2");
+        assertValue(fillRequest3.structure, ID_L2C1, "l2c1");
+        assertValue(fillRequest3.structure, ID_L2C2, "l2c2");
+        if (manually) {
+            assertHasFlags(fillRequest3.flags, FLAG_MANUAL_REQUEST);
+            assertValue(fillRequest3.structure, ID_L3C1, "");
+        } else {
+            assertThat(fillRequest3.flags).isEqualTo(0);
+            assertTextIsSanitized(fillRequest3.structure, ID_L3C1);
+        }
+        assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 3");
+
+        // Check the results.
+        expectation3.assertAutoFilled();
+
+        // 4th partition
+        // Prepare.
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 4"))
+                        .setField(ID_L4C1, "l4c1")
+                        .setField(ID_L4C2, "l4c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "l4c1")
+                .onCell(4, 2, "l4c2");
+
+        // Trigger auto-fill.
+        mActivity.triggerAutofill(manually, 4, 1);
+        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
+
+        assertValue(fillRequest4.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest4.structure, ID_L1C2, "l1c2");
+        assertValue(fillRequest4.structure, ID_L2C1, "l2c1");
+        assertValue(fillRequest4.structure, ID_L2C2, "l2c2");
+        assertValue(fillRequest4.structure, ID_L3C1, "l3c1");
+        assertValue(fillRequest4.structure, ID_L3C2, "l3c2");
+        if (manually) {
+            assertHasFlags(fillRequest4.flags, FLAG_MANUAL_REQUEST);
+            assertValue(fillRequest4.structure, ID_L4C1, "");
+        } else {
+            assertThat(fillRequest4.flags).isEqualTo(0);
+            assertTextIsSanitized(fillRequest4.structure, ID_L4C1);
+        }
+        assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 4");
+
+        // Check the results.
+        expectation4.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofill4PartitionsMixManualAndAuto() throws Exception {
+        // Set service.
+        enableService();
+
+        // 1st partition - auto
+        // Prepare.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 1"))
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger auto-fill.
+        focusCell(1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+
+        assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 1");
+
+        // Check the results.
+        expectation1.assertAutoFilled();
+
+        // 2nd partition - manual
+        // Prepare
+        // Must set text before creating expectation, and it must be a subset of the dataset values,
+        // otherwise the UI won't be shown because of filtering
+        mActivity.setText(2, 1, "l2");
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 2"))
+                        .setField(ID_L2C1, "l2c1")
+                        .setField(ID_L2C2, "l2c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 1, "l2c1")
+                .onCell(2, 2, "l2c2");
+
+        // Trigger auto-fill.
+        mActivity.forceAutofill(2, 1);
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
+
+        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
+        assertValue(fillRequest2.structure, ID_L2C1, "l2");
+        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 2");
+
+        // Check the results.
+        expectation2.assertAutoFilled();
+
+        // 3rd partition - auto
+        // Prepare.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 3"))
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+
+        // Trigger auto-fill.
+        focusCell(3, 1);
+        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
+        assertThat(fillRequest3.flags).isEqualTo(0);
+
+        assertValue(fillRequest3.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest3.structure, ID_L1C2, "l1c2");
+        assertValue(fillRequest3.structure, ID_L2C1, "l2c1");
+        assertValue(fillRequest3.structure, ID_L2C2, "l2c2");
+        assertTextIsSanitized(fillRequest3.structure, ID_L3C1);
+        assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 3");
+
+        // Check the results.
+        expectation3.assertAutoFilled();
+
+        // 4th partition - manual
+        // Must set text before creating expectation, and it must be a subset of the dataset values,
+        // otherwise the UI won't be shown because of filtering
+        mActivity.setText(4, 1, "l4");
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 4"))
+                        .setField(ID_L4C1, "l4c1")
+                        .setField(ID_L4C2, "l4c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "l4c1")
+                .onCell(4, 2, "l4c2");
+
+        // Trigger auto-fill.
+        mActivity.forceAutofill(4, 1);
+        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest4.flags, FLAG_MANUAL_REQUEST);
+
+        assertValue(fillRequest4.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest4.structure, ID_L1C2, "l1c2");
+        assertValue(fillRequest4.structure, ID_L2C1, "l2c1");
+        assertValue(fillRequest4.structure, ID_L2C2, "l2c2");
+        assertValue(fillRequest4.structure, ID_L3C1, "l3c1");
+        assertValue(fillRequest4.structure, ID_L3C2, "l3c2");
+        assertValue(fillRequest4.structure, ID_L4C1, "l4");
+        assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 4");
+
+        // Check the results.
+        expectation4.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofillBundleDataIsPassedAlong() throws Exception {
+        // Set service.
+        enableService();
+
+        final Bundle extras = new Bundle();
+        extras.putString("numbers", "4");
+
+        // Prepare 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setExtras(extras)
+                .build();
+        sReplier.addResponse(response1);
+
+        // Trigger auto-fill on 1st partition.
+        focusCell(1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+        assertThat(fillRequest1.data).isNull();
+        mUiBot.assertDatasets("l1c1");
+
+        // Prepare 2nd partition; it replaces 'number' and adds 'numbers2'
+        extras.clear();
+        extras.putString("numbers", "48");
+        extras.putString("numbers2", "1516");
+
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setExtras(extras)
+                .build();
+        sReplier.addResponse(response2);
+
+        // Trigger auto-fill on 2nd partition
+        focusCell(2, 1);
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.flags).isEqualTo(0);
+        assertWithMessage("null bundle on request 2").that(fillRequest2.data).isNotNull();
+        assertWithMessage("wrong number of extras on request 2 bundle")
+                .that(fillRequest2.data.size()).isEqualTo(1);
+        assertThat(fillRequest2.data.getString("numbers")).isEqualTo("4");
+
+        // Prepare 3nd partition; it has no extras
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
+                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
+                        .build())
+                .setExtras(null)
+                .build();
+        sReplier.addResponse(response3);
+
+        // Trigger auto-fill on 3rd partition
+        focusCell(3, 1);
+        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
+        assertThat(fillRequest3.flags).isEqualTo(0);
+        assertWithMessage("null bundle on request 3").that(fillRequest2.data).isNotNull();
+        assertWithMessage("wrong number of extras on request 3 bundle")
+                .that(fillRequest3.data.size()).isEqualTo(2);
+        assertThat(fillRequest3.data.getString("numbers")).isEqualTo("48");
+        assertThat(fillRequest3.data.getString("numbers2")).isEqualTo("1516");
+
+
+        // Prepare 4th partition; it contains just 'numbers4'
+        extras.clear();
+        extras.putString("numbers4", "2342");
+
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L4C1, "l4c1", createPresentation("l4c1"))
+                        .setField(ID_L4C2, "l4c2", createPresentation("l4c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .setExtras(extras)
+                .build();
+        sReplier.addResponse(response4);
+
+        // Trigger auto-fill on 4th partition
+        focusCell(4, 1);
+        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
+        assertThat(fillRequest4.flags).isEqualTo(0);
+        assertWithMessage("non-null bundle on request 4").that(fillRequest4.data).isNull();
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        assertWithMessage("wrong number of extras on save request bundle")
+                .that(saveRequest.data.size()).isEqualTo(1);
+        assertThat(saveRequest.data.getString("numbers4")).isEqualTo("2342");
+    }
+
+    @Test
+    public void testSaveOneSaveInfoOnFirstPartitionWithIdsOnSecond() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L2C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        mActivity.setText(2, 1, "L2C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
+    }
+
+    @Test
+    public void testSaveOneSaveInfoOnSecondPartitionWithIdsOnFirst() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
+    }
+
+    @Test
+    public void testSaveTwoSaveInfosDifferentTypes() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
+                        ID_L2C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.setText(2, 1, "L2C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
+        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
+    }
+
+    @Test
+    public void testSaveThreeSaveInfosDifferentTypes() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
+                        ID_L2C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 3rd partition.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
+                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
+                        | SAVE_DATA_TYPE_USERNAME, ID_L3C1)
+                .build();
+        sReplier.addResponse(response3);
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.setText(2, 1, "L2C1");
+        mActivity.setText(3, 1, "L3C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD,
+                SAVE_DATA_TYPE_USERNAME);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
+        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
+        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
+    }
+
+    @Test
+    public void testSaveThreeSaveInfosDifferentTypesIncludingGeneric() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_GENERIC, ID_L2C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 3rd partition.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
+                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
+                        .build())
+                .setRequiredSavableIds(
+                        SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_GENERIC | SAVE_DATA_TYPE_USERNAME,
+                        ID_L3C1)
+                .build();
+        sReplier.addResponse(response3);
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.setText(2, 1, "L2C1");
+        mActivity.setText(3, 1, "L3C1");
+        mActivity.save();
+
+        // Make sure GENERIC type is not shown on snackbar
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_USERNAME);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
+        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
+        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
+    }
+
+    @Test
+    public void testSaveMoreThanThreeSaveInfosDifferentTypes() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
+                        ID_L2C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 3rd partition.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
+                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
+                        | SAVE_DATA_TYPE_USERNAME, ID_L3C1)
+                .build();
+        sReplier.addResponse(response3);
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 4th partition.
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L4C1, "l4c1", createPresentation("l4c1"))
+                        .setField(ID_L4C2, "l4c2", createPresentation("l4c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
+                        | SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_ADDRESS, ID_L4C1)
+                .build();
+        sReplier.addResponse(response4);
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.setText(2, 1, "L2C1");
+        mActivity.setText(3, 1, "L3C1");
+        mActivity.setText(4, 1, "L4C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
+        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
+        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
+        assertValue(saveRequest.structure, ID_L4C1, "L4C1");
+    }
+
+    @Test
+    public void testIgnoredFieldsDontTriggerAutofill() throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setIgnoreFields(ID_L2C1, ID_L2C2)
+                .build();
+        sReplier.addResponse(response1);
+
+        // Trigger auto-fill on 1st partition.
+        focusCell(1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+        final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        final ViewNode p1l1c2 = assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
+        assertWithMessage("Focus on p1l1c1").that(p1l1c1.isFocused()).isTrue();
+        assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
+
+        // Make sure UI is shown on 1st partition
+        mUiBot.assertDatasets("l1c1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("l1c2");
+
+        // Make sure UI is not shown on ignored partition
+        focusCell(2, 1);
+        mUiBot.assertNoDatasets();
+        focusCellNoWindowChange(2, 2);
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset, but they don't overlap, i.e.,
+     * each {@link FillResponse} only contain fields within the partition.
+     */
+    @Test
+    public void testAutofillMultipleDatasetsNoOverlap() throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P1D1"))
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P1D2"))
+                        .setField(ID_L1C1, "L1C1")
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L2C1, "l2c1")
+                        .setField(ID_L2C2, "l2c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, "L2C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 2, "L2C2");
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L3C1, "L3C1")
+                        .setField(ID_L3C2, "L3C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "L3C1")
+                .onCell(3, 2, "L3C2");
+
+        // Trigger partition.
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L4C1, "l4c1")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L4C1, "L4C1")
+                        .setField(ID_L4C2, "L4C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "l4c1");
+
+        // Trigger partition.
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        /*
+         *  Now move focus around to make sure the proper values are displayed each time.
+         */
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        /*
+         *  Finally, autofill and check results.
+         */
+        focusCell(4, 1);
+        mUiBot.selectDataset("P4D1");
+        expectation4.assertAutoFilled();
+
+        focusCell(1, 1);
+        mUiBot.selectDataset("P1D1");
+        expectation1.assertAutoFilled();
+
+        focusCell(3, 1);
+        mUiBot.selectDataset("P3D2");
+        expectation3.assertAutoFilled();
+
+        focusCell(2, 2);
+        mUiBot.selectDataset("P2D2");
+        expectation2.assertAutoFilled();
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset, but they overlap, i.e.,
+     * some fields are present in more than one partition.
+     *
+     * <p>Whenever a new partition defines a field previously present in another partittion, that
+     * partition will "own" that field.
+     *
+     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
+     * the first.
+     */
+    @Test
+    public void testAutofillMultipleDatasetsOverlappingPicksFirst() throws Exception {
+        autofillMultipleDatasetsOverlapping(true);
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset, but they overlap, i.e.,
+     * some fields are present in more than one partition.
+     *
+     * <p>Whenever a new partition defines a field previously present in another partittion, that
+     * partition will "own" that field.
+     *
+     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
+     * the second.
+     */
+    @Test
+    public void testAutofillMultipleDatasetsOverlappingPicksSecond() throws Exception {
+        autofillMultipleDatasetsOverlapping(false);
+    }
+
+    private void autofillMultipleDatasetsOverlapping(boolean pickFirst) throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P1D1"))
+                        .setField(ID_L1C1, "1l1c1")
+                        .setField(ID_L1C2, "1l1c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P1D2"))
+                        .setField(ID_L1C1, "1L1C1")
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L1C1, "2l1c1") // from previous partition
+                        .setField(ID_L2C1, "2l2c1")
+                        .setField(ID_L2C2, "2l2c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, "2L2C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1"); // changed
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L1C2, "3l1c2")
+                        .setField(ID_L3C1, "3l3c1")
+                        .setField(ID_L3C2, "3l3c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L2C2, "3l2c2")
+                        .setField(ID_L3C1, "3L3C1")
+                        .setField(ID_L3C2, "3L3C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+
+        // Trigger partition.
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P3D1"); // changed
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P3D2"); // changed
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L1C1, "4l1c1")
+                        .setField(ID_L1C2, "4l1c2")
+                        .setField(ID_L2C1, "4l2c1")
+                        .setField(ID_L2C2, "4l2c2")
+                        .setField(ID_L3C1, "4l3c1")
+                        .setField(ID_L3C2, "4l3c2")
+                        .setField(ID_L4C1, "4l4c1")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L1C1, "4L1C1")
+                        .setField(ID_L1C2, "4L1C2")
+                        .setField(ID_L2C1, "4L2C1")
+                        .setField(ID_L2C2, "4L2C2")
+                        .setField(ID_L3C1, "4L3C1")
+                        .setField(ID_L3C2, "4L3C2")
+                        .setField(ID_L1C1, "4L1C1")
+                        .setField(ID_L4C1, "4L4C1")
+                        .setField(ID_L4C2, "4L4C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+
+        // Trigger partition.
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        /*
+         * Finally, autofill and check results.
+         */
+        final FillExpectation expectation = mActivity.expectAutofill();
+        final String chosenOne;
+        if (pickFirst) {
+            expectation
+                .onCell(1, 1, "4l1c1")
+                .onCell(1, 2, "4l1c2")
+                .onCell(2, 1, "4l2c1")
+                .onCell(2, 2, "4l2c2")
+                .onCell(3, 1, "4l3c1")
+                .onCell(3, 2, "4l3c2")
+                .onCell(4, 1, "4l4c1");
+            chosenOne = "P4D1";
+        } else {
+            expectation
+                .onCell(1, 1, "4L1C1")
+                .onCell(1, 2, "4L1C2")
+                .onCell(2, 1, "4L2C1")
+                .onCell(2, 2, "4L2C2")
+                .onCell(3, 1, "4L3C1")
+                .onCell(3, 2, "4L3C2")
+                .onCell(4, 1, "4L4C1")
+                .onCell(4, 2, "4L4C2");
+            chosenOne = "P4D2";
+        }
+
+        focusCell(4, 1);
+        mUiBot.selectDataset(chosenOne);
+        expectation.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofillMultipleAuthDatasetsInSequence() throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final IntentSender auth11 = AuthenticationActivity.createSender(getContext(), 11,
+                new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build());
+        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth11)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth12)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Focus around different fields in the partition.
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        // Autofill it...
+        mUiBot.selectDataset("P1D1");
+        // ... and assert result
+        expectation1.assertAutoFilled();
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 21);
+        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
+                new CannedDataset.Builder()
+                    .setField(ID_L2C2, "L2C2")
+                    .build());
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth21)
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth22)
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 2, "L2C2");
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Focus around different fields in the partition.
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        // Autofill it...
+        mUiBot.selectDataset("P2D2");
+        // ... and assert result
+        expectation2.assertAutoFilled();
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
+                new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build());
+        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth31)
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth32)
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+
+        // Trigger partition.
+        focusCell(3, 2);
+        sReplier.getNextFillRequest();
+
+        // Focus around different fields in the partition.
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        // Autofill it...
+        mUiBot.selectDataset("P3D1");
+        // ... and assert result
+        expectation3.assertAutoFilled();
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
+        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
+                new CannedDataset.Builder()
+                    .setField(ID_L4C1, "L4C1")
+                    .setField(ID_L4C2, "L4C2")
+                    .build());
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth41)
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth42)
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "L4C1")
+                .onCell(4, 2, "L4C2");
+
+        // Trigger partition.
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        // Focus around different fields in the partition.
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        // Autofill it...
+        mUiBot.selectDataset("P4D2");
+        // ... and assert result
+        expectation4.assertAutoFilled();
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset and all datasets require auth,
+     * but they don't overlap, i.e., each {@link FillResponse} only contain fields within the
+     * partition.
+     */
+    @Test
+    public void testAutofillMultipleAuthDatasetsNoOverlap() throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final IntentSender auth11 = AuthenticationActivity.createSender(getContext(), 11,
+                new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build());
+        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth11)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth12)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 21);
+        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
+                new CannedDataset.Builder()
+                    .setField(ID_L2C2, "L2C2")
+                    .build());
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth21)
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth22)
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 2, "L2C2");
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
+                new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build());
+        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth31)
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth32)
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+
+        // Trigger partition.
+        focusCell(3, 2);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
+        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
+                new CannedDataset.Builder()
+                    .setField(ID_L4C1, "L4C1")
+                    .setField(ID_L4C2, "L4C2")
+                    .build());
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth41)
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth42)
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "L4C1")
+                .onCell(4, 2, "L4C2");
+
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        /*
+         *  Now move focus around to make sure the proper values are displayed each time.
+         */
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        /*
+         *  Finally, autofill and check results.
+         */
+        focusCell(4, 1);
+        mUiBot.selectDataset("P4D2");
+        expectation4.assertAutoFilled();
+
+        focusCell(1, 1);
+        mUiBot.selectDataset("P1D1");
+        expectation1.assertAutoFilled();
+
+        focusCell(3, 1);
+        mUiBot.selectDataset("P3D1");
+        expectation3.assertAutoFilled();
+
+        focusCell(2, 2);
+        mUiBot.selectDataset("P2D2");
+        expectation2.assertAutoFilled();
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset and some datasets require auth,
+     * but they don't overlap, i.e., each {@link FillResponse} only contain fields within the
+     * partition.
+     */
+    @Test
+    public void testAutofillMultipleDatasetsMixedAuthNoAuthNoOverlap() throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .setPresentation(createPresentation("P1D1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth12)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
+                new CannedDataset.Builder()
+                    .setField(ID_L2C2, "L2C2")
+                    .build());
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L2C1, "l2c1")
+                        .setField(ID_L2C2, "l2c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth22)
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 2, "L2C2");
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
+                new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build());
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth31)
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L3C1, "L3C1")
+                        .setField(ID_L3C2, "L3C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+
+        // Trigger partition.
+        focusCell(3, 2);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth41)
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L4C1, "L4C1")
+                        .setField(ID_L4C2, "L4C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "L4C1")
+                .onCell(4, 2, "L4C2");
+
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        /*
+         *  Now move focus around to make sure the proper values are displayed each time.
+         */
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        /*
+         *  Finally, autofill and check results.
+         */
+        focusCell(4, 1);
+        mUiBot.selectDataset("P4D2");
+        expectation4.assertAutoFilled();
+
+        focusCell(1, 1);
+        mUiBot.selectDataset("P1D1");
+        expectation1.assertAutoFilled();
+
+        focusCell(3, 1);
+        mUiBot.selectDataset("P3D1");
+        expectation3.assertAutoFilled();
+
+        focusCell(2, 2);
+        mUiBot.selectDataset("P2D2");
+        expectation2.assertAutoFilled();
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset - some authenticated and some
+     * not - but they overlap, i.e., some fields are present in more than one partition.
+     *
+     * <p>Whenever a new partition defines a field previously present in another partittion, that
+     * partition will "own" that field.
+     *
+     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
+     * the first.
+     */
+    @Test
+    public void testAutofillMultipleAuthDatasetsOverlapPickFirst() throws Exception {
+        autofillMultipleAuthDatasetsOverlapping(true);
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset - some authenticated and some
+     * not - but they overlap, i.e., some fields are present in more than one partition.
+     *
+     * <p>Whenever a new partition defines a field previously present in another partittion, that
+     * partition will "own" that field.
+     *
+     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
+     * the second.
+     */
+    @Test
+    public void testAutofillMultipleAuthDatasetsOverlapPickSecond() throws Exception {
+        autofillMultipleAuthDatasetsOverlapping(false);
+    }
+
+    private void autofillMultipleAuthDatasetsOverlapping(boolean pickFirst) throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "1l1c1")
+                        .setField(ID_L1C2, "1l1c2")
+                        .setPresentation(createPresentation("P1D1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth12)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 22,
+                new CannedDataset.Builder()
+                    .setField(ID_L1C1, "2l1c1") // from previous partition
+                    .setField(ID_L2C1, "2l2c1")
+                    .setField(ID_L2C2, "2l2c2")
+                    .build());
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth21)
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, "2L2C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1"); // changed
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
+                new CannedDataset.Builder()
+                        .setField(ID_L1C2, "3l1c2") // from previous partition
+                        .setField(ID_L3C1, "3l3c1")
+                        .setField(ID_L3C2, "3l3c2")
+                        .build());
+        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth31)
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth32)
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+
+        // Trigger partition.
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P3D1"); // changed
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P3D2"); // changed
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41,
+                new CannedDataset.Builder()
+                        .setField(ID_L1C1, "4l1c1") // from previous partition
+                        .setField(ID_L1C2, "4l1c2") // from previous partition
+                        .setField(ID_L2C1, "4l2c1") // from previous partition
+                        .setField(ID_L2C2, "4l2c2") // from previous partition
+                        .setField(ID_L3C1, "4l3c1") // from previous partition
+                        .setField(ID_L3C2, "4l3c2") // from previous partition
+                        .setField(ID_L4C1, "4l4c1")
+                        .build());
+        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
+                new CannedDataset.Builder()
+                        .setField(ID_L1C1, "4L1C1") // from previous partition
+                        .setField(ID_L1C2, "4L1C2") // from previous partition
+                        .setField(ID_L2C1, "4L2C1") // from previous partition
+                        .setField(ID_L2C2, "4L2C2") // from previous partition
+                        .setField(ID_L3C1, "4L3C1") // from previous partition
+                        .setField(ID_L3C2, "4L3C2") // from previous partition
+                        .setField(ID_L1C1, "4L1C1") // from previous partition
+                        .setField(ID_L4C1, "4L4C1")
+                        .setField(ID_L4C2, "4L4C2")
+                        .build());
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth41)
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth42)
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+
+        // Trigger partition.
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        /*
+         * Finally, autofill and check results.
+         */
+        final FillExpectation expectation = mActivity.expectAutofill();
+        final String chosenOne;
+        if (pickFirst) {
+            expectation
+                .onCell(1, 1, "4l1c1")
+                .onCell(1, 2, "4l1c2")
+                .onCell(2, 1, "4l2c1")
+                .onCell(2, 2, "4l2c2")
+                .onCell(3, 1, "4l3c1")
+                .onCell(3, 2, "4l3c2")
+                .onCell(4, 1, "4l4c1");
+            chosenOne = "P4D1";
+        } else {
+            expectation
+                .onCell(1, 1, "4L1C1")
+                .onCell(1, 2, "4L1C2")
+                .onCell(2, 1, "4L2C1")
+                .onCell(2, 2, "4L2C2")
+                .onCell(3, 1, "4L3C1")
+                .onCell(3, 2, "4L3C2")
+                .onCell(4, 1, "4L4C1")
+                .onCell(4, 2, "4L4C2");
+            chosenOne = "P4D2";
+        }
+
+        focusCell(4, 1);
+        mUiBot.selectDataset(chosenOne);
+        expectation.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofillAllResponsesAuthenticated() throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare 1st partition.
+        final IntentSender auth1 = AuthenticationActivity.createSender(getContext(), 1,
+                new CannedFillResponse.Builder()
+                        .addDataset(new CannedDataset.Builder()
+                                .setPresentation(createPresentation("Partition 1"))
+                                .setField(ID_L1C1, "l1c1")
+                                .setField(ID_L1C2, "l1c2")
+                                .build())
+                        .build());
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .setPresentation(createPresentation("Auth 1"))
+                .setAuthentication(auth1, ID_L1C1, ID_L1C2)
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("Auth 1");
+
+        // Prepare 2nd partition.
+        final IntentSender auth2 = AuthenticationActivity.createSender(getContext(), 2,
+                new CannedFillResponse.Builder()
+                        .addDataset(new CannedDataset.Builder()
+                                .setPresentation(createPresentation("Partition 2"))
+                                .setField(ID_L2C1, "l2c1")
+                                .setField(ID_L2C2, "l2c2")
+                                .build())
+                        .build());
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .setPresentation(createPresentation("Auth 2"))
+                .setAuthentication(auth2, ID_L2C1, ID_L2C2)
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 1, "l2c1")
+                .onCell(2, 2, "l2c2");
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("Auth 2");
+
+        // Prepare 3rd partition.
+        final IntentSender auth3 = AuthenticationActivity.createSender(getContext(), 3,
+                new CannedFillResponse.Builder()
+                        .addDataset(new CannedDataset.Builder()
+                                .setPresentation(createPresentation("Partition 3"))
+                                .setField(ID_L3C1, "l3c1")
+                                .setField(ID_L3C2, "l3c2")
+                                .build())
+                        .build());
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .setPresentation(createPresentation("Auth 3"))
+                .setAuthentication(auth3, ID_L3C1, ID_L3C2)
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("Auth 3");
+
+        // Prepare 4th partition.
+        final IntentSender auth4 = AuthenticationActivity.createSender(getContext(), 4,
+                new CannedFillResponse.Builder()
+                        .addDataset(new CannedDataset.Builder()
+                                .setPresentation(createPresentation("Partition 4"))
+                                .setField(ID_L4C1, "l4c1")
+                                .setField(ID_L4C2, "l4c2")
+                                .build())
+                        .build());
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .setPresentation(createPresentation("Auth 4"))
+                .setAuthentication(auth4, ID_L4C1, ID_L4C2)
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "l4c1")
+                .onCell(4, 2, "l4c2");
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("Auth 4");
+
+        // Now play around the focus to make sure they still display the right values.
+
+        focusCell(1, 2);
+        mUiBot.assertDatasets("Auth 1");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("Auth 1");
+
+        focusCell(3, 1);
+        mUiBot.assertDatasets("Auth 3");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("Auth 3");
+
+        focusCell(2, 1);
+        mUiBot.assertDatasets("Auth 2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("Auth 4");
+
+        focusCell(2, 2);
+        mUiBot.assertDatasets("Auth 2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("Auth 4");
+
+        // Finally, autofill and check them.
+        focusCell(2, 1);
+        mUiBot.selectDataset("Auth 2");
+        mUiBot.selectDataset("Partition 2");
+        expectation2.assertAutoFilled();
+
+        focusCell(4, 1);
+        mUiBot.selectDataset("Auth 4");
+        mUiBot.selectDataset("Partition 4");
+        expectation4.assertAutoFilled();
+
+        focusCell(3, 1);
+        mUiBot.selectDataset("Auth 3");
+        mUiBot.selectDataset("Partition 3");
+        expectation3.assertAutoFilled();
+
+        focusCell(1, 1);
+        mUiBot.selectDataset("Auth 1");
+        mUiBot.selectDataset("Partition 1");
+        expectation1.assertAutoFilled();
+    }
+
+    @Test
+    public void testNoMorePartitionsAfterLimitReached() throws Exception {
+        final int maxBefore = getMaxPartitions();
+        try {
+            setMaxPartitions(1);
+            // Set service.
+            enableService();
+
+            // Prepare 1st partition.
+            final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                            .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                            .build())
+                    .build();
+            sReplier.addResponse(response1);
+
+            // Trigger autofill.
+            focusCell(1, 1);
+            sReplier.getNextFillRequest();
+
+            // Make sure UI is shown, but don't tap it.
+            mUiBot.assertDatasets("l1c1");
+            focusCell(1, 2);
+            mUiBot.assertDatasets("l1c2");
+
+            // Prepare 2nd partition.
+            final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                            .build())
+                    .build();
+            sReplier.addResponse(response2);
+
+            // Trigger autofill on 2nd partition.
+            focusCell(2, 1);
+
+            // Make sure it was ignored.
+            mUiBot.assertNoDatasets();
+
+            // Make sure 1st partition is still working.
+            focusCell(1, 2);
+            mUiBot.assertDatasets("l1c2");
+            focusCell(1, 1);
+            mUiBot.assertDatasets("l1c1");
+
+            // Prepare 3rd partition.
+            final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
+                            .build())
+                    .build();
+            sReplier.addResponse(response3);
+            // Trigger autofill on 3rd partition.
+            focusCell(3, 2);
+
+            // Make sure it was ignored.
+            mUiBot.assertNoDatasets();
+
+            // Make sure 1st partition is still working...
+            focusCell(1, 2);
+            mUiBot.assertDatasets("l1c2");
+            focusCell(1, 1);
+            mUiBot.assertDatasets("l1c1");
+
+            //...and can be autofilled.
+            final FillExpectation expectation = mActivity.expectAutofill()
+                    .onCell(1, 1, "l1c1");
+            mUiBot.selectDataset("l1c1");
+            expectation.assertAutoFilled();
+        } finally {
+            setMaxPartitions(maxBefore);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/PreFilledLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/PreFilledLoginActivityTest.java
new file mode 100644
index 0000000..9c9ca79
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/PreFilledLoginActivityTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextFromResources;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertTextOnly;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.PreFilledLoginActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.platform.test.annotations.AppModeFull;
+
+import org.junit.Test;
+
+/**
+ * Covers scenarios where the behavior is different because some fields were pre-filled.
+ */
+@AppModeFull(reason = "LoginActivityTest is enough")
+public class PreFilledLoginActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<PreFilledLoginActivity> {
+
+    private PreFilledLoginActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<PreFilledLoginActivity> getActivityRule() {
+        return new AutofillActivityTestRule<PreFilledLoginActivity>(PreFilledLoginActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testSanitization() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Change view contents.
+        mActivity.onUsernameLabel((v) -> v.setText("DA USER"));
+        mActivity.onPasswordLabel((v) -> v.setText(R.string.new_password_label));
+
+        // Trigger auto-fill.
+        mActivity.onUsername((v) -> v.requestFocus());
+
+        // Assert sanitization on fill request:
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // ...dynamic text should be sanitized.
+        assertTextIsSanitized(fillRequest.structure, ID_USERNAME_LABEL);
+
+        // ...password label should be ok because it was set from other resource id
+        assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+                "new_password_label");
+
+        // ...username and password should be ok because they were set in the SML
+        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_USERNAME),
+                "secret_agent");
+        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_PASSWORD), "T0p S3cr3t");
+
+        // Trigger save
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        mActivity.tapLogin();
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Assert sanitization on save: everything should be available!
+        assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_USERNAME_LABEL), "DA USER");
+        assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+                "new_password_label");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_USERNAME), "malkovich");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "malkovich");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerClockActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerClockActivityTest.java
new file mode 100644
index 0000000..f73fd3d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerClockActivityTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import android.autofillservice.cts.activities.TimePickerClockActivity;
+import android.autofillservice.cts.commontests.TimePickerTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "Unit test")
+public class TimePickerClockActivityTest extends TimePickerTestCase<TimePickerClockActivity> {
+
+    @Override
+    protected AutofillActivityTestRule<TimePickerClockActivity> getActivityRule() {
+        return new AutofillActivityTestRule<TimePickerClockActivity>(
+                TimePickerClockActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerSpinnerActivityTest.java
new file mode 100644
index 0000000..12818da
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerSpinnerActivityTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import android.autofillservice.cts.activities.TimePickerSpinnerActivity;
+import android.autofillservice.cts.commontests.TimePickerTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "Unit test")
+public class TimePickerSpinnerActivityTest extends TimePickerTestCase<TimePickerSpinnerActivity> {
+
+    @Override
+    protected AutofillActivityTestRule<TimePickerSpinnerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<TimePickerSpinnerActivity>(
+                TimePickerSpinnerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java
new file mode 100644
index 0000000..b41b51f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.VirtualContainerActivity.INITIAL_URL_BAR_VALUE;
+import static android.autofillservice.cts.activities.VirtualContainerView.ID_URL_BAR;
+import static android.autofillservice.cts.activities.VirtualContainerView.ID_URL_BAR2;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceCompatMode.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceCompatMode.SERVICE_PACKAGE;
+import static android.provider.Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_GLOBAL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.content.AutofillOptions;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.SaveInfo;
+
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+import com.android.compatibility.common.util.SettingsUtils;
+
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Test case for an activity containing virtual children but using the A11Y compat mode to implement
+ * the Autofill APIs.
+ */
+public class VirtualContainerActivityCompatModeTest extends VirtualContainerActivityTest {
+
+    @ClassRule
+    public static final SettingsStateChangerRule sCompatModeChanger = new SettingsStateChangerRule(
+            sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+            SERVICE_PACKAGE + "[my_url_bar]");
+
+    public VirtualContainerActivityCompatModeTest() {
+        super(true);
+    }
+
+    @After
+    public void resetCompatMode() {
+        sContext.getApplicationContext().setAutofillOptions(null);
+    }
+
+    @Override
+    protected void preActivityCreated() {
+        sContext.getApplicationContext()
+                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
+    }
+
+    @Override
+    protected void postActivityLaunched() {
+        // Set our own compat mode as well..
+        mActivity.mCustomView.setCompatMode(true);
+    }
+
+    @Override
+    protected void enableService() {
+        Helper.enableAutofillService(getContext(), SERVICE_NAME);
+    }
+
+    @Override
+    protected void disableService() {
+        Helper.disableAutofillService(getContext());
+    }
+
+    @Override
+    protected void assertUrlBarIsSanitized(ViewNode urlBar) {
+        assertTextIsSanitized(urlBar);
+        assertThat(urlBar.getWebDomain()).isEqualTo("dev.null");
+        assertThat(urlBar.getWebScheme()).isEqualTo("ftp");
+    }
+
+    @Test
+    public void testMultipleUrlBars_firstDoesNotExist() throws Exception {
+        SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+                SERVICE_PACKAGE + "[first_am_i,my_url_bar]");
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
+                .build());
+
+        // Trigger autofill.
+        focusToUsername();
+        assertDatasetShown(mActivity.mUsername, "DUDE");
+
+        // Make sure input was sanitized.
+        final FillRequest request = sReplier.getNextFillRequest();
+        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
+
+        assertUrlBarIsSanitized(urlBar);
+    }
+
+    @Test
+    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
+    public void testMultipleUrlBars_bothExist() throws Exception {
+        SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+                SERVICE_PACKAGE + "[my_url_bar,my_url_bar2]");
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
+                .build());
+
+        // Trigger autofill.
+        focusToUsername();
+        assertDatasetShown(mActivity.mUsername, "DUDE");
+
+        // Make sure input was sanitized.
+        final FillRequest request = sReplier.getNextFillRequest();
+        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
+        final ViewNode urlBar2 = findNodeByResourceId(request.structure, ID_URL_BAR2);
+
+        assertUrlBarIsSanitized(urlBar);
+        assertTextIsSanitized(urlBar2);
+    }
+
+    @Test
+    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
+    public void testFocusOnUrlBarIsIgnored() throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.requestFocus());
+
+        // Must force sleep, as there is no callback that we can wait upon.
+        SystemClock.sleep(Timeouts.FILL_TIMEOUT.ms());
+
+        sReplier.assertNoUnhandledFillRequests();
+    }
+
+    @Test
+    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
+    public void testUrlBarChangeIgnoredWhenServiceCanSave() throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Fill in some stuff
+        mActivity.mUsername.setText("foo");
+        focusToPasswordExpectNoWindowEvent();
+        mActivity.mPassword.setText("bar");
+
+        // Change URL bar before views become invisible
+        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
+                mActivity.mUrlBar, "http://null/dev");
+        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
+        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
+        urlWatcher.assertAutoFilled();
+
+        // Trigger save.
+        // TODO(b/76220569): ideally, save should be triggered by calling:
+        //
+        // setViewsInvisible(VisibilityIntegrationMode.OVERRIDE_IS_VISIBLE_TO_USER);
+        //
+        // But unfortunately that's not always working due to flakiness on showing the UI, hence
+        // we're forcing commit - after all, the point here is the the URL update above didn't
+        // cancel the session (which is the case on
+        // testUrlBarChangeCancelSessionWhenServiceCannotSave()
+        mActivity.getAutofillManager().commit();
+
+        // Assert UI is showing.
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+        final ViewNode urlBar = findNodeByResourceId(saveRequest.structure, ID_URL_BAR);
+
+        assertTextAndValue(username, "foo");
+        assertTextAndValue(password, "bar");
+        // Make sure it's the URL bar from initial session.
+        assertTextAndValue(urlBar, INITIAL_URL_BAR_VALUE);
+    }
+
+    @Test
+    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
+    public void testUrlBarChangeCancelSessionWhenServiceCannotSave() throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                    .setField(ID_USERNAME, "dude")
+                    .setField(ID_PASSWORD, "sweet")
+                    .setPresentation(createPresentation("The Dude"))
+                    .build())
+                // there's no SaveInfo here
+                .build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+        assertDatasetShown(mActivity.mUsername, "The Dude");
+
+        // Fill in some stuff
+        mActivity.mUsername.setText("foo");
+        focusToPasswordExpectNoWindowEvent();
+        mActivity.mPassword.setText("bar");
+
+        // Change URL bar before views become invisible
+        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
+                mActivity.mUrlBar, "http://null/dev");
+        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
+        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
+        urlWatcher.assertAutoFilled();
+
+        // Trigger save...
+        mActivity.getAutofillManager().commit();
+
+        // ... should not be triggered because the session was already canceled...
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
+    public void testUrlBarChangeCancelSessionWhenServiceReturnsNullResponse() throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Fill in some stuff
+        mActivity.mUsername.setText("foo");
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+        focusToPasswordExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+        mActivity.mPassword.setText("bar");
+
+        // Change URL bar before views become invisible
+        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
+                mActivity.mUrlBar, "http://null/dev");
+        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
+        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
+        urlWatcher.assertAutoFilled();
+
+        // Trigger save...
+        mActivity.getAutofillManager().commit();
+
+        // ... should not be triggered because the session was already canceled...
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityTest.java
new file mode 100644
index 0000000..ea7c61d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityTest.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.VirtualContainerView.ID_URL_BAR;
+import static android.autofillservice.cts.activities.VirtualContainerView.LABEL_CLASS;
+import static android.autofillservice.cts.activities.VirtualContainerView.TEXT_CLASS;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertTextOnly;
+import static android.autofillservice.cts.testcore.Helper.dumpStructure;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.VirtualContainerActivity;
+import android.autofillservice.cts.activities.VirtualContainerView;
+import android.autofillservice.cts.activities.VirtualContainerView.Line;
+import android.autofillservice.cts.activities.VirtualContainerView.VisibilityIntegrationMode;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.graphics.Rect;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+import android.text.InputType;
+import android.view.ViewGroup;
+import android.view.autofill.AutofillManager;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Test case for an activity containing virtual children, either using the explicit Autofill APIs
+ * or Compat mode.
+ */
+public class VirtualContainerActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<VirtualContainerActivity> {
+
+    // TODO(b/74256300): remove when fixed it :-)
+    private static final boolean BUG_74256300_FIXED = false;
+
+    private final boolean mCompatMode;
+    private AutofillActivityTestRule<VirtualContainerActivity> mActivityRule;
+    protected VirtualContainerActivity mActivity;
+
+    public VirtualContainerActivityTest() {
+        this(false);
+    }
+
+    protected VirtualContainerActivityTest(boolean compatMode) {
+        mCompatMode = compatMode;
+    }
+
+    /**
+     * Hook for subclass to customize test before activity is created.
+     */
+    protected void preActivityCreated() {}
+
+    /**
+     * Hook for subclass to customize activity after it's launched.
+     */
+    protected void postActivityLaunched() {}
+
+    @Override
+    protected AutofillActivityTestRule<VirtualContainerActivity> getActivityRule() {
+        if (mActivityRule == null) {
+            mActivityRule = new AutofillActivityTestRule<VirtualContainerActivity>(
+                    VirtualContainerActivity.class) {
+                @Override
+                protected void beforeActivityLaunched() {
+                    preActivityCreated();
+                }
+
+                @Override
+                protected void afterActivityLaunched() {
+                    mActivity = getActivity();
+                    postActivityLaunched();
+                }
+            };
+
+        }
+        return mActivityRule;
+    }
+
+    @Test
+    public void testAutofillSync() throws Exception {
+        autofillTest(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillSync() is enough")
+    public void testAutofillAsync() throws Exception {
+        skipTestOnCompatMode();
+
+        autofillTest(false);
+    }
+
+    @Test
+    public void testAutofill_appContext() throws Exception {
+        mActivity.mCustomView.setAutofillManager(mActivity.getApplicationContext());
+        autofillTest(true);
+        // Validation check to make sure autofill is enabled in the application context
+        assertThat(mActivity.getApplicationContext().getSystemService(AutofillManager.class)
+                .isEnabled()).isTrue();
+    }
+
+    /**
+     * Focus to username and expect window event
+     */
+    void focusToUsername() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.mUsername.changeFocus(true));
+    }
+
+    /**
+     * Focus to username and expect no autofill window event
+     */
+    void focusToUsernameExpectNoWindowEvent() throws Throwable {
+        // TODO: should use waitForWindowChange() if we can filter out event of app Activity itself.
+        mActivityRule.runOnUiThread(() -> mActivity.mUsername.changeFocus(true));
+    }
+
+    /**
+     * Focus to password and expect window event
+     */
+    void focusToPassword() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.mPassword.changeFocus(true));
+    }
+
+    /**
+     * Focus to password and expect no autofill window event
+     */
+    void focusToPasswordExpectNoWindowEvent() throws Throwable {
+        // TODO should use waitForWindowChange() if we can filter out event of app Activity itself.
+        mActivityRule.runOnUiThread(() -> mActivity.mPassword.changeFocus(true));
+    }
+
+    /**
+     * Tests autofilling the virtual views, using the sync / async version of ViewStructure.addChild
+     */
+    private void autofillTest(boolean sync) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
+                .setField(ID_PASSWORD, "sweet", createPresentation("SWEET"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+        mActivity.mCustomView.setSync(sync);
+
+        // Trigger auto-fill.
+        focusToUsername();
+        assertDatasetShown(mActivity.mUsername, "DUDE");
+
+        // Play around with focus to make sure picker is properly drawn.
+        if (BUG_74256300_FIXED || !mCompatMode) {
+            focusToPassword();
+            assertDatasetShown(mActivity.mPassword, "SWEET");
+
+            focusToUsername();
+            assertDatasetShown(mActivity.mUsername, "DUDE");
+        }
+
+        // Make sure input was sanitized.
+        final FillRequest request = sReplier.getNextFillRequest();
+        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
+        final ViewNode usernameLabel = findNodeByResourceId(request.structure, ID_USERNAME_LABEL);
+        final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
+        final ViewNode passwordLabel = findNodeByResourceId(request.structure, ID_PASSWORD_LABEL);
+        final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
+
+        assertUrlBarIsSanitized(urlBar);
+        assertTextIsSanitized(username);
+        assertTextIsSanitized(password);
+        assertLabel(usernameLabel, "Username");
+        assertLabel(passwordLabel, "Password");
+
+        assertThat(usernameLabel.getClassName()).isEqualTo(LABEL_CLASS);
+        assertThat(username.getClassName()).isEqualTo(TEXT_CLASS);
+        assertThat(passwordLabel.getClassName()).isEqualTo(LABEL_CLASS);
+        assertThat(password.getClassName()).isEqualTo(TEXT_CLASS);
+
+        assertThat(username.getIdEntry()).isEqualTo(ID_USERNAME);
+        assertThat(password.getIdEntry()).isEqualTo(ID_PASSWORD);
+
+        assertThat(username.getInputType())
+                .isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL);
+        assertThat(usernameLabel.getInputType()).isEqualTo(0);
+        assertThat(password.getInputType())
+                .isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+        assertThat(passwordLabel.getInputType()).isEqualTo(0);
+
+        final String[] autofillHints = username.getAutofillHints();
+        final boolean hasCompatModeFlag = (request.flags
+                & android.service.autofill.FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST) != 0;
+        if (mCompatMode) {
+            assertThat(hasCompatModeFlag).isTrue();
+            assertThat(autofillHints).isNull();
+            assertThat(username.getHtmlInfo()).isNull();
+            assertThat(password.getHtmlInfo()).isNull();
+        } else {
+            assertThat(hasCompatModeFlag).isFalse();
+            // Make sure order is preserved and dupes not removed.
+            assertThat(autofillHints).asList()
+                    .containsExactly("c", "a", "a", "b", "a", "a")
+                    .inOrder();
+            try {
+                VirtualContainerView.assertHtmlInfo(username);
+                VirtualContainerView.assertHtmlInfo(password);
+            } catch (AssertionError | RuntimeException e) {
+                dumpStructure("HtmlInfo failed", request.structure);
+                throw e;
+            }
+        }
+
+        // Make sure initial focus was properly set.
+        assertWithMessage("Username node is not focused").that(username.isFocused()).isTrue();
+        assertWithMessage("Password node is focused").that(password.isFocused()).isFalse();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("DUDE");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillSync() is enough")
+    public void testAutofillTwoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .setPresentation(createPresentation("THE DUDE"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("DUDE", "SWEET");
+
+        // Trigger auto-fill.
+        focusToUsername();
+        sReplier.getNextFillRequest();
+        assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
+
+        // Play around with focus to make sure picker is properly drawn.
+        if (BUG_74256300_FIXED || !mCompatMode) {
+            focusToPassword();
+            assertDatasetShown(mActivity.mPassword, "The Dude", "THE DUDE");
+            focusToUsername();
+            assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
+        }
+
+        // Auto-fill it.
+        mUiBot.selectDataset("THE DUDE");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofillOverrideDispatchProvideAutofillStructure() throws Exception {
+        mActivity.mCustomView.setOverrideDispatchProvideAutofillStructure(true);
+        autofillTest(true);
+    }
+
+    @Test
+    public void testAutofillManuallyOneDataset() throws Exception {
+        skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        mActivity.requestAutofill(mActivity.mUsername);
+        sReplier.getNextFillRequest();
+
+        // Select datatest.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception {
+        autofillManuallyTwoDatasets(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception {
+        autofillManuallyTwoDatasets(false);
+    }
+
+    private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
+        skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "jenny")
+                        .setField(ID_PASSWORD, "8675309")
+                        .setPresentation(createPresentation("Jenny"))
+                        .build())
+                .build());
+        if (pickFirst) {
+            mActivity.expectAutoFill("dude", "sweet");
+        } else {
+            mActivity.expectAutoFill("jenny", "8675309");
+
+        }
+
+        // Trigger auto-fill.
+        mActivity.getSystemService(AutofillManager.class).requestAutofill(
+                mActivity.mCustomView, mActivity.mUsername.text.id,
+                mActivity.mUsername.getAbsCoordinates());
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        final UiObject2 picker = assertDatasetShown(mActivity.mUsername, "The Dude", "Jenny");
+        mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofillCallbacks() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        focusToUsername();
+        sReplier.getNextFillRequest();
+
+        callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+
+        // Change focus
+        focusToPassword();
+        callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+        callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mPassword.text.id);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackDisabled() throws Throwable {
+        // Set service.
+        disableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+
+        // Assert callback was called
+        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackNoDatasets() throws Throwable {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        mUiBot.assertNoDatasetsEver();
+
+        // Assert callback was called
+        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackNoDatasetsButSaveInfo() throws Throwable {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Autofill it.
+        mUiBot.assertNoDatasetsEver();
+
+        // Assert callback was called
+        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+
+        // Make sure save is not triggered
+        mActivity.getAutofillManager().commit();
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    public void testSaveDialogNotShownWhenBackIsPressed() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        focusToUsername();
+        sReplier.getNextFillRequest();
+        assertDatasetShown(mActivity.mUsername, "The Dude");
+
+        mUiBot.pressBack();
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    public void testSave_childViewsGone_notifyAfm() throws Throwable {
+        saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API);
+    }
+
+    @Test
+    public void testSave_childViewsGone_updateView() throws Throwable {
+        saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API);
+    }
+
+    @Test
+    @Ignore("Disabled until b/73493342 is fixed")
+    public void testSave_parentViewGone() throws Throwable {
+        saveTest(CommitType.PARENT_VIEW_GONE);
+    }
+
+    @Test
+    public void testSave_appCallsCommit() throws Throwable {
+        saveTest(CommitType.EXPLICIT_COMMIT);
+    }
+
+    @Test
+    public void testSave_submitButtonClicked() throws Throwable {
+        saveTest(CommitType.SUBMIT_BUTTON_CLICKED);
+    }
+
+    enum CommitType {
+        CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API,
+        CHILDREN_VIEWS_GONE_IS_VISIBLE_API,
+        PARENT_VIEW_GONE,
+        EXPLICIT_COMMIT,
+        SUBMIT_BUTTON_CLICKED
+    }
+
+    private void saveTest(CommitType commitType) throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD);
+
+        switch (commitType) {
+            case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API:
+            case CHILDREN_VIEWS_GONE_IS_VISIBLE_API:
+            case PARENT_VIEW_GONE:
+                response.setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
+                break;
+            case EXPLICIT_COMMIT:
+                // does nothing
+                break;
+            case SUBMIT_BUTTON_CLICKED:
+                response
+                    .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
+                    .setSaveTriggerId(mActivity.mCustomView.mLoginButtonId);
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + commitType);
+        }
+        sReplier.addResponse(response.build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Fill in some stuff
+        mActivity.mUsername.setText("foo");
+        focusToPasswordExpectNoWindowEvent();
+        mActivity.mPassword.setText("bar");
+
+        // Trigger save.
+        switch (commitType) {
+            case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API:
+                setViewsInvisible(VisibilityIntegrationMode.NOTIFY_AFM);
+                break;
+            case CHILDREN_VIEWS_GONE_IS_VISIBLE_API:
+                setViewsInvisible(VisibilityIntegrationMode.OVERRIDE_IS_VISIBLE_TO_USER);
+                break;
+            case PARENT_VIEW_GONE:
+                mActivity.runOnUiThread(() -> {
+                    final ViewGroup parent = (ViewGroup) mActivity.mCustomView.getParent();
+                    parent.removeView(mActivity.mCustomView);
+                });
+                break;
+            case EXPLICIT_COMMIT:
+                mActivity.getAutofillManager().commit();
+                break;
+            case SUBMIT_BUTTON_CLICKED:
+                mActivity.mCustomView.clickLogin();
+                break;
+            default:
+                throw new IllegalArgumentException("unknown type: " + commitType);
+        }
+
+        // Assert UI is showing.
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+
+        assertTextAndValue(username, "foo");
+        assertTextAndValue(password, "bar");
+    }
+
+    protected void setViewsInvisible(VisibilityIntegrationMode mode) {
+        mActivity.mUsername.setVisibilityIntegrationMode(mode);
+        mActivity.mPassword.setVisibilityIntegrationMode(mode);
+        mActivity.mUsername.changeVisibility(false);
+        mActivity.mPassword.changeVisibility(false);
+    }
+
+    // NOTE: tests where save is not shown only makes sense when calling commit() explicitly,
+    // otherwise the test could pass but the UI is still shown *after* the app is committed.
+    // We could still test them by explicitly committing and then checking that the Save UI is not
+    // shown again, but then we wouldn't be effectively testing that the context was committed
+
+    @Test
+    public void testSaveNotShown_noUserInput() throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.getAutofillManager().commit();
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
+    public void testSaveNotShown_initialValues_noUserInput() throws Throwable {
+        // Prepare activitiy.
+        mActivity.mUsername.setText("foo");
+        mActivity.mPassword.setText("bar");
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.getAutofillManager().commit();
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
+    public void testSaveNotShown_initialValues_noUserInput_serviceDatasets() throws Throwable {
+        // Prepare activitiy.
+        mActivity.mUsername.setText("foo");
+        mActivity.mPassword.setText("bar");
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+        assertDatasetShown(mActivity.mUsername, "The Dude");
+
+        // Trigger save.
+        mActivity.getAutofillManager().commit();
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
+    public void testSaveNotShown_userInputMatchesDatasets() throws Throwable {
+        // Prepare activitiy.
+        mActivity.mUsername.setText("foo");
+        mActivity.mPassword.setText("bar");
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "foo")
+                        .setField(ID_PASSWORD, "bar")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+        assertDatasetShown(mActivity.mUsername, "The Dude");
+
+        // Trigger save.
+        mActivity.getAutofillManager().commit();
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    public void testDatasetFiltering() throws Throwable {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(createPresentation(aa))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(createPresentation(ab))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "b")
+                        .setPresentation(createPresentation(b))
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        assertDatasetShown(mActivity.mUsername, aa, ab, b);
+
+        // Only two datasets start with 'a'
+        mActivity.mUsername.setText("a");
+        assertDatasetShown(mActivity.mUsername, aa, ab);
+
+        // Only one dataset start with 'aa'
+        mActivity.mUsername.setText("aa");
+        assertDatasetShown(mActivity.mUsername, aa);
+
+        // Only two datasets start with 'a'
+        mActivity.mUsername.setText("a");
+        assertDatasetShown(mActivity.mUsername, aa, ab);
+
+        // With no filter text all datasets should be shown
+        mActivity.mUsername.setText("");
+        assertDatasetShown(mActivity.mUsername, aa, ab, b);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        mActivity.mUsername.setText("aaa");
+        callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+        mUiBot.assertNoDatasets();
+    }
+
+    /**
+     * Asserts the dataset picker is properly displayed in a give line.
+     */
+    protected UiObject2 assertDatasetShown(Line line, String... expectedDatasets)
+            throws Exception {
+        boolean autofillViewBoundsMatches = !Helper.isAutofillWindowFullScreen(mContext);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets(expectedDatasets);
+        final Rect pickerBounds = datasetPicker.getVisibleBounds();
+        final Rect fieldBounds = line.getAbsCoordinates();
+        if (autofillViewBoundsMatches) {
+            assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds,
+                    fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom);
+            assertWithMessage("horizontal coordinates don't match; picker=%s, field=%s",
+                    pickerBounds, fieldBounds).that(pickerBounds.left).isEqualTo(fieldBounds.left);
+        }
+        return datasetPicker;
+    }
+
+    protected void assertLabel(ViewNode node, String expectedValue) {
+        if (mCompatMode) {
+            // Compat mode doesn't set AutofillValue of non-editable fields
+            assertTextOnly(node, expectedValue);
+        } else {
+            assertTextAndValue(node, expectedValue);
+        }
+    }
+
+    protected void assertUrlBarIsSanitized(ViewNode urlBar) {
+        assertTextIsSanitized(urlBar);
+        assertThat(urlBar.getWebDomain()).isNull();
+        assertThat(urlBar.getWebScheme()).isNull();
+    }
+
+
+    private void skipTestOnCompatMode() {
+        assumeTrue("test not applicable when on compat mode", !mCompatMode);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/WebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/WebViewActivityTest.java
new file mode 100644
index 0000000..00215ba
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/WebViewActivityTest.java
@@ -0,0 +1,590 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.activities.WebViewActivity.ID_OUTSIDE1;
+import static android.autofillservice.cts.activities.WebViewActivity.ID_OUTSIDE2;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.MyWebView;
+import android.autofillservice.cts.activities.WebViewActivity;
+import android.autofillservice.cts.commontests.AbstractWebViewTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.IdMode;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.ViewStructure.HtmlInfo;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class WebViewActivityTest extends AbstractWebViewTestCase<WebViewActivity> {
+
+    private static final String TAG = "WebViewActivityTest";
+
+    private WebViewActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<WebViewActivity> getActivityRule() {
+        return new AutofillActivityTestRule<WebViewActivity>(WebViewActivity.class) {
+
+            // TODO(b/111838239): latest WebView implementation calls AutofillManager.isEnabled() to
+            // disable autofill for optimization when it returns false, and unfortunately the value
+            // returned by that method does not change when the service is enabled / disabled, so we
+            // need to start enable the service before launching the activity.
+            // Once that's fixed, remove this overridden method.
+            @Override
+            protected void beforeActivityLaunched() {
+                super.beforeActivityLaunched();
+                Log.i(TAG, "Setting service before launching the activity");
+                enableService();
+            }
+
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillOneDataset() is enough")
+    public void testAutofillNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        mActivity.loadWebView(mUiBot);
+
+        // Set expectations.
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        sReplier.getNextFillRequest();
+
+        // Assert not shown.
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    public void testAutofillOneDataset() throws Exception {
+        autofillOneDatasetTest(false);
+    }
+
+    @Ignore("blocked on b/74793485")
+    @Test
+    @AppModeFull(reason = "testAutofillOneDataset() is enough")
+    public void testAutofillOneDataset_usingAppContext() throws Exception {
+        autofillOneDatasetTest(true);
+    }
+
+    private void autofillOneDatasetTest(boolean usesAppContext) throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot, usesAppContext);
+        // Validation check to make sure autofill is enabled in the application context
+        Helper.assertAutofillEnabled(myWebView.getContext(), true);
+
+        // Set expectations.
+        myWebView.expectAutofill("dude", "sweet");
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(HTML_NAME_USERNAME, "dude")
+                .setField(HTML_NAME_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Change focus around.
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+        mActivity.getUsernameLabel().click();
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
+        mUiBot.assertNoDatasets();
+        mActivity.getPasswordInput().click();
+        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
+
+        // Now Autofill it.
+        mUiBot.selectDataset(datasetPicker, "The Dude");
+        myWebView.assertAutofilled();
+        mUiBot.assertNoDatasets();
+        callback.assertUiHiddenEvent(myWebView, passwordChildId);
+
+        // Assert structure passed to service.
+        try {
+            final ViewNode webViewNode =
+                    Helper.findWebViewNodeByFormName(fillRequest.structure, "FORM AM I");
+            assertThat(webViewNode.getClassName()).isEqualTo("android.webkit.WebView");
+            assertThat(webViewNode.getWebDomain()).isEqualTo(WebViewActivity.FAKE_DOMAIN);
+            assertThat(webViewNode.getWebScheme()).isEqualTo("https");
+
+            final ViewNode usernameNode =
+                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_USERNAME);
+            Helper.assertTextIsSanitized(usernameNode);
+            final HtmlInfo usernameHtmlInfo = Helper.assertHasHtmlTag(usernameNode, "input");
+            Helper.assertHasAttribute(usernameHtmlInfo, "type", "text");
+            Helper.assertHasAttribute(usernameHtmlInfo, "name", "username");
+            assertThat(usernameNode.isFocused()).isTrue();
+            assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
+            assertThat(usernameNode.getHint()).isEqualTo("There's no place like a holder");
+
+            final ViewNode passwordNode =
+                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_PASSWORD);
+            Helper.assertTextIsSanitized(passwordNode);
+            final HtmlInfo passwordHtmlInfo = Helper.assertHasHtmlTag(passwordNode, "input");
+            Helper.assertHasAttribute(passwordHtmlInfo, "type", "password");
+            Helper.assertHasAttribute(passwordHtmlInfo, "name", "password");
+            assertThat(passwordNode.getAutofillHints()).asList()
+                    .containsExactly("current-password");
+            assertThat(passwordNode.getHint()).isEqualTo("Holder it like it cannnot passer a word");
+            assertThat(passwordNode.isFocused()).isFalse();
+        } catch (RuntimeException | Error e) {
+            Helper.dumpStructure("failed on testAutofillOneDataset()", fillRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    public void testSaveOnly() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        mActivity.loadWebView(mUiBot);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        sReplier.getNextFillRequest();
+
+        // Assert not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
+        } else {
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
+        }
+        mActivity.getLoginButton().click();
+
+        // Assert save UI shown.
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode usernameNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
+        if (INJECT_EVENTS) {
+            Helper.assertTextAndValue(usernameNode, "u");
+            Helper.assertTextAndValue(passwordNode, "p");
+        } else {
+            Helper.assertTextAndValue(usernameNode, "DUDE");
+            Helper.assertTextAndValue(passwordNode, "SWEET");
+        }
+    }
+
+    @Test
+    public void testAutofillAndSave() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+
+        // Set expectations.
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        myWebView.expectAutofill("dude", "sweet");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(HTML_NAME_USERNAME, "dude")
+                        .setField(HTML_NAME_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        // Assert structure passed to service.
+        final ViewNode usernameNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_USERNAME);
+        Helper.assertTextIsSanitized(usernameNode);
+        assertThat(usernameNode.isFocused()).isTrue();
+        assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
+        final ViewNode passwordNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_PASSWORD);
+        Helper.assertTextIsSanitized(passwordNode);
+        assertThat(passwordNode.getAutofillHints()).asList().containsExactly("current-password");
+        assertThat(passwordNode.isFocused()).isFalse();
+
+        // Autofill it.
+        mUiBot.selectDataset("The Dude");
+        myWebView.assertAutofilled();
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
+        } else {
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
+        }
+        mActivity.getLoginButton().click();
+
+        // Assert save UI shown.
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode usernameNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
+        if (INJECT_EVENTS) {
+            Helper.assertTextAndValue(usernameNode2, "dudeu");
+            Helper.assertTextAndValue(passwordNode2, "sweetp");
+        } else {
+            Helper.assertTextAndValue(usernameNode2, "DUDE");
+            Helper.assertTextAndValue(passwordNode2, "SWEET");
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillAndSave() is enough")
+    public void testAutofillAndSave_withExternalViews_loadWebViewFirst() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load views
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+        mActivity.loadOutsideViews();
+
+        // Set expectations.
+        myWebView.expectAutofill("dude", "sweet");
+        final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
+                mActivity.mOutside1, "duder");
+        final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
+                mActivity.mOutside2, "sweeter");
+        mActivity.mOutside1.addTextChangedListener(outside1Watcher);
+        mActivity.mOutside2.addTextChangedListener(outside2Watcher);
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
+                        .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
+                        .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
+                        .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("USER");
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        // Assert structure passed to service.
+        final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_USERNAME);
+        Helper.assertTextIsSanitized(usernameFillNode);
+        assertThat(usernameFillNode.isFocused()).isTrue();
+        assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
+        final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_PASSWORD);
+        Helper.assertTextIsSanitized(passwordFillNode);
+        assertThat(passwordFillNode.getAutofillHints()).asList()
+                .containsExactly("current-password");
+        assertThat(passwordFillNode.isFocused()).isFalse();
+
+        final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextIsSanitized(outside1FillNode);
+        final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextIsSanitized(outside2FillNode);
+
+        // Move focus around to make sure UI is shown accordingly
+        mActivity.clearFocus();
+        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
+        mUiBot.assertDatasets("OUT1");
+        callback.assertUiShownEvent(mActivity.mOutside1);
+
+        mActivity.clearFocus();
+        mActivity.getPasswordInput().click();
+        callback.assertUiHiddenEvent(mActivity.mOutside1);
+        mUiBot.assertDatasets("PASS");
+        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        mActivity.clearFocus();
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, passwordChildId);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        // Autofill it.
+        mUiBot.selectDataset(datasetPicker, "OUT2");
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+
+        myWebView.assertAutofilled();
+        outside1Watcher.assertAutoFilled();
+        outside2Watcher.assertAutoFilled();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
+        } else {
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
+        }
+        mActivity.runOnUiThread(() -> {
+            mActivity.mOutside1.setText("DUDER");
+            mActivity.mOutside2.setText("SWEETER");
+        });
+
+        mActivity.getLoginButton().click();
+
+        // Assert save UI shown.
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
+        if (INJECT_EVENTS) {
+            Helper.assertTextAndValue(usernameSaveNode, "dudeu");
+            Helper.assertTextAndValue(passwordSaveNode, "sweetp");
+        } else {
+            Helper.assertTextAndValue(usernameSaveNode, "DUDE");
+            Helper.assertTextAndValue(passwordSaveNode, "SWEET");
+        }
+
+        final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextAndValue(outside1SaveNode, "DUDER");
+        final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
+    }
+
+
+    @Test
+    @Ignore("blocked on b/69461853")
+    @AppModeFull(reason = "testAutofillAndSave() is enough")
+    public void testAutofillAndSave_withExternalViews_loadExternalViewsFirst() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load outside views
+        mActivity.loadOutsideViews();
+
+        // Set expectations.
+        final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
+                mActivity.mOutside1, "duder");
+        final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
+                mActivity.mOutside2, "sweeter");
+        mActivity.mOutside1.addTextChangedListener(outside1Watcher);
+        mActivity.mOutside2.addTextChangedListener(outside2Watcher);
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        sReplier.setIdMode(IdMode.RESOURCE_ID);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
+                        .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("OUT1");
+        callback.assertUiShownEvent(mActivity.mOutside1);
+
+        // Move focus around to make sure UI is shown accordingly
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(mActivity.mOutside1);
+        mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        // Assert structure passed to service.
+        final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextIsSanitized(outside1FillNode);
+        final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextIsSanitized(outside2FillNode);
+
+        // Now load Webiew
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+
+        // Set expectations
+        myWebView.expectAutofill("dude", "sweet");
+        sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
+                        .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+        mUiBot.assertDatasets("USER");
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        // Move focus around to make sure UI is shown accordingly
+        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
+        mUiBot.assertDatasets("OUT1");
+        callback.assertUiShownEvent(mActivity.mOutside1);
+
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(mActivity.mOutside1);
+        mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        mActivity.getPasswordInput().click();
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+        mUiBot.assertDatasets("PASS");
+        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, passwordChildId);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        // Assert structure passed to service.
+        final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
+                HTML_NAME_USERNAME);
+        Helper.assertTextIsSanitized(usernameFillNode);
+        assertThat(usernameFillNode.isFocused()).isTrue();
+        assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
+        final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
+                HTML_NAME_PASSWORD);
+        Helper.assertTextIsSanitized(passwordFillNode);
+        assertThat(passwordFillNode.getAutofillHints()).asList()
+                .containsExactly("current-password");
+        assertThat(passwordFillNode.isFocused()).isFalse();
+
+        // Autofill external views (2nd partition)
+        mUiBot.selectDataset(datasetPicker, "OUT2");
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+        outside1Watcher.assertAutoFilled();
+        outside2Watcher.assertAutoFilled();
+
+        // Autofill Webview (1st partition)
+        mActivity.getUsernameInput().click();
+        callback.assertUiShownEventForVirtualChild(myWebView);
+        mUiBot.selectDataset("USER");
+        myWebView.assertAutofilled();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
+        } else {
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
+        }
+        mActivity.runOnUiThread(() -> {
+            mActivity.mOutside1.setText("DUDER");
+            mActivity.mOutside2.setText("SWEETER");
+        });
+
+        mActivity.getLoginButton().click();
+
+        // Assert save UI shown.
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
+        if (INJECT_EVENTS) {
+            Helper.assertTextAndValue(usernameSaveNode, "dudeu");
+            Helper.assertTextAndValue(passwordSaveNode, "sweetp");
+        } else {
+            Helper.assertTextAndValue(usernameSaveNode, "DUDE");
+            Helper.assertTextAndValue(passwordSaveNode, "SWEET");
+        }
+
+        final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextAndValue(outside1SaveNode, "DUDER");
+        final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/DatasetFilteringInlineTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/DatasetFilteringInlineTest.java
index 036e744..e0e7662 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/DatasetFilteringInlineTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/DatasetFilteringInlineTest.java
@@ -16,11 +16,12 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
 
-import android.autofillservice.cts.DatasetFilteringTest;
-import android.autofillservice.cts.Helper;
+import android.autofillservice.cts.commontests.DatasetFilteringTest;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
 
 import org.junit.rules.TestRule;
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedAuthTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedAuthTest.java
index bb81399..f95e458 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedAuthTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedAuthTest.java
@@ -18,18 +18,18 @@
 
 import static android.app.Activity.RESULT_CANCELED;
 import static android.app.Activity.RESULT_OK;
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.augmented.AugmentedAuthActivity;
-import android.autofillservice.cts.augmented.AugmentedAutofillAutoActivityLaunchTestCase;
-import android.autofillservice.cts.augmented.AugmentedLoginActivity;
-import android.autofillservice.cts.augmented.CannedAugmentedFillResponse;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService;
+import android.autofillservice.cts.activities.AugmentedAuthActivity;
+import android.autofillservice.cts.activities.AugmentedLoginActivity;
+import android.autofillservice.cts.commontests.AugmentedAutofillAutoActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService;
 import android.content.IntentSender;
 import android.service.autofill.Dataset;
 import android.view.autofill.AutofillId;
@@ -204,5 +204,11 @@
         mUiBot.selectByRelativeId(AugmentedAuthActivity.ID_AUTH_ACTIVITY_BUTTON);
         mUiBot.waitForIdle();
         assertThat(unField.getText().toString()).isEqualTo("");
+
+        // Return from the auth activity to login activity, if the login onResume() is prior to
+        // the test finished, there is another FillRequest() will be received. Because it may
+        // notifyViewEntered() in onResume().
+        mUiBot.assertShownByRelativeId(ID_USERNAME);
+        sAugmentedReplier.getNextFillRequest();
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
index 332c645..9a897f6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
@@ -16,25 +16,26 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.NULL_DATASET_ID;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetShown;
-import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.CLIENT_STATE_KEY;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.CLIENT_STATE_VALUE;
+import static android.autofillservice.cts.testcore.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.CLIENT_STATE_KEY;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.CLIENT_STATE_VALUE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.NULL_DATASET_ID;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetShown;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.MyAutofillCallback;
-import android.autofillservice.cts.augmented.AugmentedAutofillAutoActivityLaunchTestCase;
-import android.autofillservice.cts.augmented.AugmentedLoginActivity;
-import android.autofillservice.cts.augmented.CannedAugmentedFillResponse;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.autofillservice.cts.activities.AugmentedLoginActivity;
+import android.autofillservice.cts.commontests.AugmentedAutofillAutoActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
 import android.platform.test.annotations.AppModeFull;
 import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillEventHistory.Event;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
index 7ebe26d..46fcb1d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
@@ -16,27 +16,30 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_PASSWORD;
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_USERNAME;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
 
 import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.MyWebView;
-import android.autofillservice.cts.WebViewActivity;
-import android.autofillservice.cts.augmented.AugmentedAutofillAutoActivityLaunchTestCase;
-import android.autofillservice.cts.augmented.CannedAugmentedFillResponse;
+import android.autofillservice.cts.activities.MyWebView;
+import android.autofillservice.cts.activities.WebViewActivity;
+import android.autofillservice.cts.commontests.AugmentedAutofillAutoActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
 import android.support.test.uiautomator.UiObject2;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.autofill.AutofillId;
 
+import androidx.test.filters.FlakyTest;
+
 import org.junit.Test;
 
+@FlakyTest(bugId = 162372863)
 public class InlineAugmentedWebViewActivityTest extends
         AugmentedAutofillAutoActivityLaunchTestCase<WebViewActivity> {
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
index b39f549..5a8cec4 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
@@ -18,24 +18,25 @@
 
 import static android.app.Activity.RESULT_CANCELED;
 import static android.app.Activity.RESULT_OK;
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.UNUSED_AUTOFILL_VALUE;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.UNUSED_AUTOFILL_VALUE;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.autofillservice.cts.AbstractLoginActivityTestCase;
-import android.autofillservice.cts.AuthenticationActivity;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.autofillservice.cts.UiBot;
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.UiBot;
 import android.content.IntentSender;
 import android.platform.test.annotations.AppModeFull;
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
index 595b7ea..63af0aa 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
@@ -16,23 +16,24 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.NULL_DATASET_ID;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetShown;
-import static android.autofillservice.cts.Helper.assertFillEventForSaveShown;
-import static android.autofillservice.cts.Helper.assertNoDeprecatedClientState;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.NULL_DATASET_ID;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetShown;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForSaveShown;
+import static android.autofillservice.cts.testcore.Helper.assertNoDeprecatedClientState;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.FillEventHistoryCommonTestCase;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService;
-import android.autofillservice.cts.LoginActivity;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.commontests.FillEventHistoryCommonTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
 import android.platform.test.annotations.AppModeFull;
 import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillEventHistory.Event;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
index f46dd19..1ec2ced 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
@@ -16,14 +16,15 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
 
-import android.autofillservice.cts.AbstractLoginActivityTestCase;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.Helper;
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
 
 import org.junit.Test;
 import org.junit.rules.TestRule;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
index 3ca3f34..809dd06 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
@@ -16,15 +16,15 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.Timeouts.MOCK_IME_TIMEOUT_MS;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.Timeouts.MOCK_IME_TIMEOUT_MS;
 
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 
@@ -34,13 +34,14 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.app.PendingIntent;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.DummyActivity;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService;
-import android.autofillservice.cts.LoginActivityCommonTestCase;
-import android.autofillservice.cts.NonAutofillableActivity;
-import android.autofillservice.cts.UsernameOnlyActivity;
+import android.autofillservice.cts.activities.DummyActivity;
+import android.autofillservice.cts.activities.NonAutofillableActivity;
+import android.autofillservice.cts.activities.UsernameOnlyActivity;
+import android.autofillservice.cts.commontests.LoginActivityCommonTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.Bundle;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
index 42f4f16..576e42b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
@@ -16,21 +16,22 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_COMMIT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_COMMIT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 
-import android.autofillservice.cts.AutoFillServiceTestCase;
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService;
-import android.autofillservice.cts.SimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
 import android.support.test.uiautomator.UiObject2;
 
 import androidx.annotation.NonNull;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java
deleted file mode 100644
index af53383..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts.inline;
-
-import static android.autofillservice.cts.Timeouts.DATASET_PICKER_NOT_SHOWN_NAPTIME_MS;
-import static android.autofillservice.cts.Timeouts.LONG_PRESS_MS;
-import static android.autofillservice.cts.Timeouts.UI_TIMEOUT;
-
-import android.autofillservice.cts.UiBot;
-import android.content.pm.PackageManager;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.Direction;
-import android.support.test.uiautomator.UiObject2;
-
-import com.android.compatibility.common.util.RequiredFeatureRule;
-import com.android.compatibility.common.util.Timeout;
-import com.android.cts.mockime.MockIme;
-
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-
-/**
- * UiBot for the inline suggestion.
- */
-public final class InlineUiBot extends UiBot {
-
-    private static final String TAG = "AutoFillInlineCtsUiBot";
-    public static final String SUGGESTION_STRIP_DESC = "MockIme Inline Suggestion View";
-
-    private static final BySelector SUGGESTION_STRIP_SELECTOR = By.desc(SUGGESTION_STRIP_DESC);
-
-    private static final RequiredFeatureRule REQUIRES_IME_RULE = new RequiredFeatureRule(
-            PackageManager.FEATURE_INPUT_METHODS);
-
-    public InlineUiBot() {
-        this(UI_TIMEOUT);
-    }
-
-    public InlineUiBot(Timeout defaultTimeout) {
-        super(defaultTimeout);
-    }
-
-    public static RuleChain annotateRule(TestRule rule) {
-        return RuleChain.outerRule(REQUIRES_IME_RULE).around(rule);
-    }
-
-    @Override
-    public void assertNoDatasets() throws Exception {
-        assertNoDatasetsEver();
-    }
-
-    @Override
-    public void assertNoDatasetsEver() throws Exception {
-        assertNeverShown("suggestion strip", SUGGESTION_STRIP_SELECTOR,
-                DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
-    }
-
-    /**
-     * Selects the suggestion in the {@link MockIme}'s suggestion strip by the given text.
-     */
-    public void selectSuggestion(String name) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        final UiObject2 dataset = strip.findObject(By.text(name));
-        if (dataset == null) {
-            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
-        }
-        dataset.click();
-    }
-
-    @Override
-    public void selectDataset(String name) throws Exception {
-        selectSuggestion(name);
-    }
-
-    @Override
-    public void longPressSuggestion(String name) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        final UiObject2 dataset = strip.findObject(By.text(name));
-        if (dataset == null) {
-            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
-        }
-        dataset.click(LONG_PRESS_MS);
-    }
-
-    @Override
-    public UiObject2 assertDatasets(String...names) throws Exception {
-        final UiObject2 picker = findSuggestionStrip(UI_TIMEOUT);
-        return assertDatasets(picker, names);
-    }
-
-    @Override
-    public void assertSuggestion(String name) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        final UiObject2 dataset = strip.findObject(By.text(name));
-        if (dataset == null) {
-            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
-        }
-    }
-
-    @Override
-    public void assertNoSuggestion(String name) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        final UiObject2 dataset = strip.findObject(By.text(name));
-        if (dataset != null) {
-            throw new AssertionError("has dataset " + name + " in " + getChildrenAsText(strip));
-        }
-    }
-
-    @Override
-    public void scrollSuggestionView(Direction direction, int speed) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        strip.fling(direction, speed);
-    }
-
-    private UiObject2 findSuggestionStrip(Timeout timeout) throws Exception {
-        return waitForObject(SUGGESTION_STRIP_SELECTOR, timeout);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineWebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineWebViewActivityTest.java
index 63cf648..45c5915 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineWebViewActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineWebViewActivityTest.java
@@ -16,32 +16,36 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_PASSWORD;
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_USERNAME;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.AbstractWebViewTestCase;
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.autofillservice.cts.MyWebView;
-import android.autofillservice.cts.WebViewActivity;
+import android.autofillservice.cts.activities.MyWebView;
+import android.autofillservice.cts.activities.WebViewActivity;
+import android.autofillservice.cts.commontests.AbstractWebViewTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
 import android.support.test.uiautomator.UiObject2;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.ViewStructure.HtmlInfo;
 
+import androidx.test.filters.FlakyTest;
+
 import org.junit.Test;
 import org.junit.rules.TestRule;
 
+@FlakyTest(bugId = 162372863)
 public class InlineWebViewActivityTest extends AbstractWebViewTestCase<WebViewActivity> {
 
     private static final String TAG = "InlineWebViewActivityTest";
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InstrumentedAutoFillServiceInlineEnabled.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InstrumentedAutoFillServiceInlineEnabled.java
deleted file mode 100644
index a931c53..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InstrumentedAutoFillServiceInlineEnabled.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts.inline;
-
-import android.autofillservice.cts.InstrumentedAutoFillService;
-import android.service.autofill.AutofillService;
-
-/**
- * Implementation of {@link AutofillService} that has inline suggestions support enabled.
- */
-public class InstrumentedAutoFillServiceInlineEnabled extends InstrumentedAutoFillService {
-    @SuppressWarnings("hiding")
-    static final String SERVICE_PACKAGE = "android.autofillservice.cts";
-    @SuppressWarnings("hiding")
-    static final String SERVICE_CLASS = "InstrumentedAutoFillServiceInlineEnabled";
-    @SuppressWarnings("hiding")
-    static final String SERVICE_NAME = SERVICE_PACKAGE + "/.inline." + SERVICE_CLASS;
-
-    public InstrumentedAutoFillServiceInlineEnabled() {
-        sInstance.set(this);
-        sServiceLabel = SERVICE_CLASS;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/AutofillSaveDialogTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/AutofillSaveDialogTest.java
new file mode 100644
index 0000000..05c42c9
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/AutofillSaveDialogTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.SimpleAfterLoginActivity;
+import android.autofillservice.cts.activities.SimpleBeforeLoginActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+
+import org.junit.Test;
+
+/**
+ * Tests whether autofill save dialog is shown as expected.
+ */
+public class AutofillSaveDialogTest extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+    @Test
+    public void testShowSaveUiWhenLaunchActivityWithFlagClearTopAndSingleTop() throws Exception {
+        // Set service.
+        enableService();
+
+        // Start SimpleBeforeLoginActivity before login activity.
+        startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
+                Intent.FLAG_ACTIVITY_NEW_TASK);
+        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+        // Start LoginActivity.
+        startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
+                /* flags= */ 0);
+        mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+                .build());
+
+        // Trigger autofill on username.
+        LoginActivity loginActivity = LoginActivity.getCurrentActivity();
+        loginActivity.onUsername(View::requestFocus);
+
+        // Wait for fill request to be processed.
+        sReplier.getNextFillRequest();
+
+        // Set data.
+        loginActivity.onUsername((v) -> v.setText("test"));
+
+        // Start SimpleAfterLoginActivity after login activity.
+        startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class, /* flags= */ 0);
+        mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_USERNAME);
+
+        // Restart SimpleBeforeLoginActivity with CLEAR_TOP and SINGLE_TOP.
+        startActivityWithFlag(SimpleAfterLoginActivity.getCurrentActivity(),
+                SimpleBeforeLoginActivity.class,
+                Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+        // Verify save ui dialog.
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
+    }
+
+    @Test
+    public void testShowSaveUiWhenLaunchActivityWithFlagClearTaskAndNewTask() throws Exception {
+        // Set service.
+        enableService();
+
+        // Start SimpleBeforeLoginActivity before login activity.
+        startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
+                Intent.FLAG_ACTIVITY_NEW_TASK);
+        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+        // Start LoginActivity.
+        startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
+                /* flags= */ 0);
+        mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+                .build());
+
+        // Trigger autofill on username.
+        LoginActivity loginActivity = LoginActivity.getCurrentActivity();
+        loginActivity.onUsername(View::requestFocus);
+
+        // Wait for fill request to be processed.
+        sReplier.getNextFillRequest();
+
+        // Set data.
+        loginActivity.onUsername((v) -> v.setText("test"));
+
+        // Start SimpleAfterLoginActivity with CLEAR_TASK and NEW_TASK after login activity.
+        startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class,
+                Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
+
+        // Verify save ui dialog.
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
+    }
+
+    private void startActivityWithFlag(Context context, Class<?> clazz, int flags) {
+        final Intent intent = new Intent(context, clazz);
+        intent.setFlags(flags);
+        context.startActivity(intent);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionDateTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionDateTest.java
new file mode 100644
index 0000000..faabab9
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionDateTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.activities.AbstractDatePickerActivity.ID_DATE_PICKER;
+import static android.autofillservice.cts.activities.AbstractDatePickerActivity.ID_OUTPUT;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.DatePickerSpinnerActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.icu.text.SimpleDateFormat;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.DateTransformation;
+import android.service.autofill.DateValueSanitizer;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+
+import java.util.Calendar;
+
+@AppModeFull(reason = "Service-specific test")
+public class CustomDescriptionDateTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<DatePickerSpinnerActivity> {
+
+    private DatePickerSpinnerActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<DatePickerSpinnerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DatePickerSpinnerActivity>(
+                DatePickerSpinnerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testCustomSave() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0),
+                            ID_DATE_PICKER);
+                    builder.setCustomDescription(new CustomDescription
+                            .Builder(newTemplate(R.layout.two_horizontal_text_fields))
+                            .addChild(R.id.first,
+                                    new DateTransformation(id, new SimpleDateFormat("MM/yyyy")))
+                            .addChild(R.id.second,
+                                    new DateTransformation(id, new SimpleDateFormat("MM-yy")))
+                            .build());
+                })
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onOutput((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it.
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save.
+        mActivity.setDate(2010, Calendar.DECEMBER, 12);
+        mActivity.tapOk();
+
+        // First, make sure the UI is shown...
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Then, make sure it does have the custom view on it...
+        final UiObject2 staticText = saveUi.findObject(By.res(mPackageName, "static_text"));
+        assertThat(staticText).isNotNull();
+        assertThat(staticText.getText()).isEqualTo("YO:");
+
+        // Finally, assert the custom lines are shown
+        mUiBot.assertChild(saveUi, "first", (o) -> assertThat(o.getText()).isEqualTo("12/2010"));
+        mUiBot.assertChild(saveUi, "second", (o) -> assertThat(o.getText()).isEqualTo("12-10"));
+    }
+
+    @Test
+    public void testSaveSameValue_usingSanitization() throws Exception {
+        sanitizationTest(true);
+    }
+
+    @Test
+    public void testSaveSameValue_withoutSanitization() throws Exception {
+        sanitizationTest(false);
+    }
+
+    private void sanitizationTest(boolean withSanitization) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(Calendar.YEAR, 2012);
+        cal.set(Calendar.MONTH, Calendar.DECEMBER);
+
+        // Set expectations.
+
+        // NOTE: ID_OUTPUT is used to trigger autofill, but it's value will be automatically
+        // changed, hence we need to set the expected value as the formated one. Ideally
+        // we shouldn't worry about that, but that would require creating a new activitiy with
+        // a custom edit text that uses date autofill values...
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("The end of the world"))
+                        .setField(ID_OUTPUT, "2012/11/25")
+                        .setField(ID_DATE_PICKER, cal.getTimeInMillis())
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER);
+
+        if (withSanitization) {
+            response.setSaveInfoVisitor((contexts, builder) -> {
+                final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_DATE_PICKER);
+                builder.addSanitizer(new DateValueSanitizer(new SimpleDateFormat("MM/yyyy")), id);
+            });
+        }
+        sReplier.addResponse(response.build());
+
+        // Trigger autofill.
+        mActivity.onOutput((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The end of the world");
+
+        // Manually set same values as dataset.
+        mActivity.onOutput((v) -> v.setText("whatever"));
+        mActivity.setDate(2012, Calendar.DECEMBER, 25);
+        mActivity.tapOk();
+
+        // Verify save behavior.
+        if (withSanitization) {
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        } else {
+            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        }
+    }
+
+    private RemoteViews newTemplate(int resourceId) {
+        return new RemoteViews(getContext().getPackageName(), resourceId);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionTest.java
new file mode 100644
index 0000000..ed7da54
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionTest.java
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Visitor;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.FillContext;
+import android.service.autofill.ImageTransformation;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.TextValueSanitizer;
+import android.service.autofill.Validator;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.junit.Test;
+
+import java.util.function.BiFunction;
+import java.util.regex.Pattern;
+
+@AppModeFull(reason = "Service-specific test")
+public class CustomDescriptionTest extends AbstractLoginActivityTestCase {
+
+    /**
+     * Base test
+     *
+     * @param descriptionBuilder method to build a custom description
+     * @param uiVerifier         Ran when the custom description is shown
+     */
+    private void testCustomDescription(
+            @NonNull BiFunction<AutofillId, AutofillId, CustomDescription> descriptionBuilder,
+            @Nullable Runnable uiVerifier) throws Exception {
+        enableService();
+
+        // Set response with custom description
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME, ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.setCustomDescription(descriptionBuilder.apply(usernameId, passwordId));
+                })
+                .build());
+
+        // Trigger autofill with custom description
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("usernm"));
+        mActivity.onPassword((v) -> v.setText("passwd"));
+        mActivity.tapLogin();
+
+        if (uiVerifier != null) {
+            uiVerifier.run();
+        }
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        sReplier.getNextSaveRequest();
+    }
+
+    @Test
+    public void testSanitizationBeforeBatchUpdates() throws Exception {
+        enableService();
+
+        // Set response with custom description
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final RemoteViews presentation =
+                            newTemplate(R.layout.two_horizontal_text_fields);
+
+                    final AutofillId usernameId =
+                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
+
+                    // Validator for sanitization
+                    final Validator validCondition =
+                            new RegexValidator(usernameId, Pattern.compile("user"));
+
+                    final RemoteViews update = newTemplate(-666); // layout id not really used
+                    update.setTextViewText(R.id.first, "batch updated");
+
+                    final CustomDescription customDescription = new CustomDescription
+                            .Builder(presentation)
+                            .batchUpdate(validCondition,
+                                    new BatchUpdates.Builder().updateTemplate(update).build())
+                            .build();
+                    builder
+                        .addSanitizer(new TextValueSanitizer(Pattern.compile("USERNAME"), "user"),
+                                usernameId)
+                        .setCustomDescription(customDescription);
+
+                })
+                .build());
+
+        // Trigger autofill with custom description
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
+        mActivity.tapLogin();
+
+        assertSaveUiIsShownWithTwoLines("batch updated");
+    }
+
+    @Test
+    public void testSanitizationBeforeTransformations() throws Exception {
+        enableService();
+
+        // Set response with custom description
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final RemoteViews presentation =
+                            newTemplate(R.layout.two_horizontal_text_fields);
+
+                    final AutofillId usernameId =
+                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
+
+                    // Transformation
+                    final CharSequenceTransformation trans = new CharSequenceTransformation
+                            .Builder(usernameId, Pattern.compile("user"), "transformed")
+                            .build();
+
+                    final CustomDescription customDescription = new CustomDescription
+                            .Builder(presentation)
+                            .addChild(R.id.first, trans)
+                            .build();
+                    builder
+                        .addSanitizer(new TextValueSanitizer(Pattern.compile("USERNAME"), "user"),
+                                usernameId)
+                        .setCustomDescription(customDescription);
+
+                })
+                .build());
+
+        // Trigger autofill with custom description
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
+        mActivity.tapLogin();
+
+        assertSaveUiIsShownWithTwoLines("transformed");
+    }
+
+    @Test
+    public void validTransformation() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans1 = new CharSequenceTransformation
+                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
+                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+                    .build();
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans2 = new ImageTransformation
+                    .Builder(usernameId, Pattern.compile(".*"),
+                    R.drawable.android).build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans1)
+                    .addChild(R.id.img, trans2)
+                    .build();
+        }, () -> assertSaveUiIsShownWithTwoLines("usernm..wd"));
+    }
+
+    @Test
+    public void validTransformationWithOneTemplateUpdate() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans1 = new CharSequenceTransformation
+                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
+                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+                    .build();
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans2 = new ImageTransformation
+                    .Builder(usernameId, Pattern.compile(".*"),
+                    R.drawable.android).build();
+            RemoteViews update = newTemplate(0); // layout id not really used
+            update.setViewVisibility(R.id.second, View.GONE);
+            Validator condition = new RegexValidator(usernameId, Pattern.compile(".*"));
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans1)
+                    .addChild(R.id.img, trans2)
+                    .batchUpdate(condition,
+                            new BatchUpdates.Builder().updateTemplate(update).build())
+                    .build();
+        }, () -> assertSaveUiIsShownWithJustOneLine("usernm..wd"));
+    }
+
+    @Test
+    public void validTransformationWithMultipleTemplateUpdates() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans1 = new CharSequenceTransformation.Builder(usernameId,
+                    Pattern.compile("(.*)"), "$1")
+                            .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+                            .build();
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans2 = new ImageTransformation.Builder(usernameId,
+                    Pattern.compile(".*"), R.drawable.android)
+                    .build();
+
+            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+            Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
+
+            // Line 1 updates
+            RemoteViews update1 = newTemplate(666); // layout id not really used
+            update1.setContentDescription(R.id.first, "First am I"); // valid
+            RemoteViews update2 = newTemplate(0); // layout id not really used
+            update2.setViewVisibility(R.id.first, View.GONE); // invalid
+
+            // Line 2 updates
+            RemoteViews update3 = newTemplate(-666); // layout id not really used
+            update3.setTextViewText(R.id.second, "First of his second name"); // valid
+            RemoteViews update4 = newTemplate(0); // layout id not really used
+            update4.setTextViewText(R.id.second, "SECOND of his second name"); // invalid
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans1)
+                    .addChild(R.id.img, trans2)
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder().updateTemplate(update1).build())
+                    .batchUpdate(invalidCondition,
+                            new BatchUpdates.Builder().updateTemplate(update2).build())
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder().updateTemplate(update3).build())
+                    .batchUpdate(invalidCondition,
+                            new BatchUpdates.Builder().updateTemplate(update4).build())
+                    .build();
+        }, () -> assertSaveUiWithLinesIsShown(
+                (line1) -> assertWithMessage("Wrong content description for line1")
+                        .that(line1.getContentDescription()).isEqualTo("First am I"),
+                (line2) -> assertWithMessage("Wrong text for line2").that(line2.getText())
+                        .isEqualTo("First of his second name"),
+                null));
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_noConditionPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.NONE_PASS);
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_secondConditionPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.SECOND_PASS);
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_thirdConditionPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.THIRD_PASS);
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_allConditionsPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.ALL_PASS);
+    }
+
+    private enum BatchUpdatesConditionType {
+        NONE_PASS,
+        SECOND_PASS,
+        THIRD_PASS,
+        ALL_PASS
+    }
+
+    /**
+     * Tests a custom description that has 3 transformations, one applied directly and the other
+     * 2 in batch updates.
+     *
+     * @param conditionsType defines which batch updates conditions will pass.
+     */
+    private void multipleBatchUpdatesTest(BatchUpdatesConditionType conditionsType)
+            throws Exception {
+
+        final boolean line2Pass = conditionsType == BatchUpdatesConditionType.SECOND_PASS
+                || conditionsType == BatchUpdatesConditionType.ALL_PASS;
+        final boolean line3Pass = conditionsType == BatchUpdatesConditionType.THIRD_PASS
+                || conditionsType == BatchUpdatesConditionType.ALL_PASS;
+
+        final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
+                .that(line1.getText()).isEqualTo("L1-u");
+
+        final Visitor<UiObject2> line2Visitor;
+        if (line2Pass) {
+            line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
+                    .that(line2.getText()).isEqualTo("L2-u");
+        } else {
+            line2Visitor = null;
+        }
+
+        final Visitor<UiObject2> line3Visitor;
+        if (line3Pass) {
+            line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
+                    .that(line3.getText()).isEqualTo("L3-p");
+        } else {
+            line3Visitor = null;
+        }
+
+        testCustomDescription((usernameId, passwordId) -> {
+            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+            Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
+            Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
+
+            final RemoteViews presentation =
+                    newTemplate(R.layout.three_horizontal_text_fields_last_two_invisible);
+
+            final CharSequenceTransformation line1Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
+                        .build();
+
+            final CharSequenceTransformation line2Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
+                        .build();
+            final RemoteViews line2Updates = newTemplate(666); // layout id not really used
+            line2Updates.setViewVisibility(R.id.second, View.VISIBLE);
+
+            final CharSequenceTransformation line3Transformation =
+                    new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
+                        .build();
+            final RemoteViews line3Updates = newTemplate(666); // layout id not really used
+            line3Updates.setViewVisibility(R.id.third, View.VISIBLE);
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, line1Transformation)
+                    .batchUpdate(line2Pass ? validCondition : invalidCondition,
+                            new BatchUpdates.Builder()
+                            .transformChild(R.id.second, line2Transformation)
+                            .updateTemplate(line2Updates)
+                            .build())
+                    .batchUpdate(line3Pass ? validCondition : invalidCondition,
+                            new BatchUpdates.Builder()
+                            .transformChild(R.id.third, line3Transformation)
+                            .updateTemplate(line3Updates)
+                            .build())
+                    .build();
+        }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
+    }
+
+    @Test
+    public void testBatchUpdatesApplyUpdateFirstThenTransformations() throws Exception {
+
+        final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
+                .that(line1.getText()).isEqualTo("L1-u");
+        final Visitor<UiObject2> line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
+                .that(line2.getText()).isEqualTo("L2-u");
+        final Visitor<UiObject2> line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
+                .that(line3.getText()).isEqualTo("L3-p");
+
+        testCustomDescription((usernameId, passwordId) -> {
+            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+            Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
+
+            final RemoteViews presentation =
+                    newTemplate(R.layout.two_horizontal_text_fields);
+
+            final CharSequenceTransformation line1Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
+                        .build();
+
+            final CharSequenceTransformation line2Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
+                        .build();
+
+            final CharSequenceTransformation line3Transformation =
+                    new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
+                        .build();
+            final RemoteViews line3Presentation = newTemplate(R.layout.third_line_only);
+            final RemoteViews line3Updates = newTemplate(666); // layout id not really used
+            line3Updates.addView(R.id.parent, line3Presentation);
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, line1Transformation)
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder()
+                            .transformChild(R.id.second, line2Transformation)
+                            .build())
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder()
+                            .updateTemplate(line3Updates)
+                            .transformChild(R.id.third, line3Transformation)
+                            .build())
+                    .build();
+        }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
+    }
+
+    @Test
+    public void badImageTransformation() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans = new ImageTransformation.Builder(usernameId,
+                    Pattern.compile(".*"), 1).build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.img, trans)
+                    .build();
+        }, () -> assertSaveUiWithCustomDescriptionIsShown());
+    }
+
+    @Test
+    public void unusedImageTransformation() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans = new ImageTransformation
+                    .Builder(usernameId, Pattern.compile("invalid"), R.drawable.android)
+                    .build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.img, trans)
+                    .build();
+        }, () -> assertSaveUiWithCustomDescriptionIsShown());
+    }
+
+    @Test
+    public void applyImageTransformationToTextView() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans = new ImageTransformation
+                    .Builder(usernameId, Pattern.compile(".*"), R.drawable.android)
+                    .build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans)
+                    .build();
+        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
+    }
+
+    @Test
+    public void failFirstFailAll() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans = new CharSequenceTransformation
+                    .Builder(usernameId, Pattern.compile("(.*)"), "$42")
+                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+                    .build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans)
+                    .build();
+        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
+    }
+
+    @Test
+    public void failSecondFailAll() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans = new CharSequenceTransformation
+                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
+                    .addField(passwordId, Pattern.compile(".*(..)"), "..$42")
+                    .build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans)
+                    .build();
+        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
+    }
+
+    @Test
+    public void applyCharSequenceTransformationToImageView() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans = new CharSequenceTransformation
+                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
+                    .build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.img, trans)
+                    .build();
+        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
+    }
+
+    private void multipleTransformationsForSameFieldTest(boolean matchFirst) throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    // Set response with custom description
+                    final AutofillId usernameId =
+                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
+                    final CharSequenceTransformation firstTrans = new CharSequenceTransformation
+                            .Builder(usernameId, Pattern.compile("(marco)"), "polo")
+                            .build();
+                    final CharSequenceTransformation secondTrans = new CharSequenceTransformation
+                            .Builder(usernameId, Pattern.compile("(MARCO)"), "POLO")
+                            .build();
+                    final RemoteViews presentation =
+                            newTemplate(R.layout.two_horizontal_text_fields);
+                    final CustomDescription customDescription =
+                            new CustomDescription.Builder(presentation)
+                            .addChild(R.id.first, firstTrans)
+                            .addChild(R.id.first, secondTrans)
+                            .build();
+                    builder.setCustomDescription(customDescription);
+                })
+                .build());
+
+        // Trigger autofill with custom description
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        final String username = matchFirst ? "marco" : "MARCO";
+        mActivity.onUsername((v) -> v.setText(username));
+        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
+        mActivity.tapLogin();
+
+        final String expectedText = matchFirst ? "polo" : "POLO";
+        assertSaveUiIsShownWithTwoLines(expectedText);
+    }
+
+    @Test
+    public void applyMultipleTransformationsForSameField_matchFirst() throws Exception {
+        multipleTransformationsForSameFieldTest(true);
+    }
+
+    @Test
+    public void applyMultipleTransformationsForSameField_matchSecond() throws Exception {
+        multipleTransformationsForSameFieldTest(false);
+    }
+
+    private RemoteViews newTemplate(int resourceId) {
+        return new RemoteViews(getContext().getPackageName(), resourceId);
+    }
+
+    private UiObject2 assertSaveUiShowing() {
+        try {
+            return mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void assertSaveUiWithoutCustomDescriptionIsShown() {
+        // First make sure the UI is shown...
+        final UiObject2 saveUi = assertSaveUiShowing();
+
+        // Then make sure it does not have the custom view on it.
+        assertWithMessage("found static_text on SaveUI (%s)", mUiBot.getChildrenAsText(saveUi))
+            .that(saveUi.findObject(By.res(mPackageName, "static_text"))).isNull();
+    }
+
+    private UiObject2 assertSaveUiWithCustomDescriptionIsShown() {
+        // First make sure the UI is shown...
+        final UiObject2 saveUi = assertSaveUiShowing();
+
+        // Then make sure it does have the custom view on it...
+        final UiObject2 staticText = saveUi.findObject(By.res(mPackageName, "static_text"));
+        assertThat(staticText).isNotNull();
+        assertThat(staticText.getText()).isEqualTo("YO:");
+
+        return saveUi;
+    }
+
+    /**
+     * Asserts the save ui only has {@code first} and {@code second} lines (i.e, {@code third} is
+     * invisible), but only {@code first} has text.
+     */
+    private UiObject2 assertSaveUiIsShownWithTwoLines(String expectedTextOnFirst) {
+        return assertSaveUiWithLinesIsShown(
+                (line1) -> assertWithMessage("Wrong text for child with id 'first'")
+                        .that(line1.getText()).isEqualTo(expectedTextOnFirst),
+                (line2) -> assertWithMessage("Wrong text for child with id 'second'")
+                        .that(line2.getText()).isNull(),
+                null);
+    }
+
+    /**
+     * Asserts the save ui only has {@code first} line (i.e., {@code second} and {@code third} are
+     * invisible).
+     */
+    private void assertSaveUiIsShownWithJustOneLine(String expectedTextOnFirst) {
+        assertSaveUiWithLinesIsShown(
+                (line1) -> assertWithMessage("Wrong text for child with id 'first'")
+                        .that(line1.getText()).isEqualTo(expectedTextOnFirst),
+                null, null);
+    }
+
+    private UiObject2 assertSaveUiWithLinesIsShown(@Nullable Visitor<UiObject2> line1Visitor,
+            @Nullable Visitor<UiObject2> line2Visitor, @Nullable Visitor<UiObject2> line3Visitor) {
+        final UiObject2 saveUi = assertSaveUiWithCustomDescriptionIsShown();
+        mUiBot.assertChild(saveUi, "first", line1Visitor);
+        mUiBot.assertChild(saveUi, "second", line2Visitor);
+        mUiBot.assertChild(saveUi, "third", line3Visitor);
+        return saveUi;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java
new file mode 100644
index 0000000..8282fec
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_HIDE;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_PASSWORD_MASKED;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_PASSWORD_PLAIN;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_SHOW;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_USERNAME_MASKED;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_USERNAME_PLAIN;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.newCustomDescriptionWithHiddenFields;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.FillContext;
+import android.service.autofill.OnClickAction;
+import android.service.autofill.VisibilitySetterAction;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+
+import java.util.regex.Pattern;
+
+/**
+ * Integration tests for the {@link OnClickAction} implementations.
+ */
+@AppModeFull(reason = "Service-specific test")
+public class OnClickActionTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<SimpleSaveActivity> {
+
+    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
+
+    private SimpleSaveActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
+        return new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testHideAndShow() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId usernameId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+
+                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
+                            .Builder(usernameId, MATCH_ALL, "$1").build();
+                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
+                            .Builder(passwordId, MATCH_ALL, "$1").build();
+                    builder.setCustomDescription(newCustomDescriptionWithHiddenFields()
+                            .addChild(R.id.username_plain, usernameTrans)
+                            .addChild(R.id.password_plain, passwordTrans)
+                            .addOnClickAction(R.id.show, new VisibilitySetterAction
+                                    .Builder(R.id.hide, View.VISIBLE)
+                                    .setVisibility(R.id.show, View.GONE)
+                                    .setVisibility(R.id.username_plain, View.VISIBLE)
+                                    .setVisibility(R.id.password_plain, View.VISIBLE)
+                                    .setVisibility(R.id.username_masked, View.GONE)
+                                    .setVisibility(R.id.password_masked, View.GONE)
+                                    .build())
+                            .addOnClickAction(R.id.hide, new VisibilitySetterAction
+                                    .Builder(R.id.show, View.VISIBLE)
+                                    .setVisibility(R.id.hide, View.GONE)
+                                    .setVisibility(R.id.username_masked, View.VISIBLE)
+                                    .setVisibility(R.id.password_masked, View.VISIBLE)
+                                    .setVisibility(R.id.username_plain, View.GONE)
+                                    .setVisibility(R.id.password_plain, View.GONE)
+                                    .build())
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("42");
+            mActivity.mPassword.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Assert initial UI is hidden the password.
+        final UiObject2 showButton = assertHidden(saveUi);
+
+        // Then tap SHOW and assert it's showing how
+        showButton.click();
+        final UiObject2 hideButton = assertShown(saveUi);
+
+        // Hide again
+        hideButton.click();
+        assertHidden(saveUi);
+
+        // Rinse-and repeat a couple times
+        showButton.click(); assertShown(saveUi);
+        hideButton.click(); assertHidden(saveUi);
+        showButton.click(); assertShown(saveUi);
+        hideButton.click(); assertHidden(saveUi);
+
+        // Then save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "108");
+    }
+
+    /**
+     * Asserts that the Save UI is in the hiding the password field, returning the {@code SHOW}
+     * button.
+     */
+    private UiObject2 assertHidden(UiObject2 saveUi) throws Exception {
+        // Username
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME_MASKED, "****");
+        assertInvisible(saveUi, ID_USERNAME_PLAIN);
+
+        // Password
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_MASKED, "....");
+        assertInvisible(saveUi, ID_PASSWORD_PLAIN);
+
+        // Buttons
+        assertInvisible(saveUi, ID_HIDE);
+        return mUiBot.assertChildText(saveUi, ID_SHOW, "SHOW");
+    }
+
+    /**
+     * Asserts that the Save UI is in the showing the password field, returning the {@code HIDE}
+     * button.
+     */
+    private UiObject2 assertShown(UiObject2 saveUi) throws Exception {
+        // Username
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME_PLAIN, "42");
+        assertInvisible(saveUi, ID_USERNAME_MASKED);
+
+        // Password
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_PLAIN, "108");
+        assertInvisible(saveUi, ID_PASSWORD_MASKED);
+
+        // Buttons
+        assertInvisible(saveUi, ID_SHOW);
+        return mUiBot.assertChildText(saveUi, ID_HIDE, "HIDE");
+    }
+
+    private void assertInvisible(UiObject2 saveUi, String resourceId) {
+        mUiBot.assertGoneByRelativeId(saveUi, resourceId, Timeouts.UI_TIMEOUT);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java
new file mode 100644
index 0000000..1e38ae4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java
@@ -0,0 +1,775 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.activities.OptionalSaveActivity.ID_ADDRESS1;
+import static android.autofillservice.cts.activities.OptionalSaveActivity.ID_ADDRESS2;
+import static android.autofillservice.cts.activities.OptionalSaveActivity.ID_CITY;
+import static android.autofillservice.cts.activities.OptionalSaveActivity.ID_FAVORITE_COLOR;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.activities.OptionalSaveActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.Visitor;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.junit.Test;
+
+/**
+ * Test case for an activity that contains 4 fields, but the service is only interested in 2-3 of
+ * them for Save:
+ *
+ * <ul>
+ *   <li>Address 1: required
+ *   <li>Address 2: required
+ *   <li>City: optional
+ *   <li>Favorite Color: don't care - LOL
+ * </ul>
+ */
+@AppModeFull(reason = "Service-specific test")
+public class OptionalSaveActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<OptionalSaveActivity> {
+
+    private static final boolean EXPECT_NO_SAVE_UI = false;
+    private static final boolean EXPECT_SAVE_UI = true;
+
+    private OptionalSaveActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<OptionalSaveActivity> getActivityRule() {
+        return new AutofillActivityTestRule<OptionalSaveActivity>(OptionalSaveActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    /**
+     * Creates a standard builder common to all tests.
+     */
+    private CannedFillResponse.Builder newResponseBuilder() {
+        return new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_CITY)
+                .setOptionalSavableIds(ID_ADDRESS2);
+    }
+
+    @Test
+    public void testNoAutofillSaveAll() throws Exception {
+        noAutofillSaveOnChangeTest(() -> {
+            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
+            mActivity.mAddress2.setText("Simpsons House"); // not required
+            mActivity.mCity.setText("Springfield"); // required
+            mActivity.mFavoriteColor.setText("Yellow"); // lol
+        }, (s) -> {
+            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1), "742 Evergreen Terrace");
+            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
+            assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
+            assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Yellow");
+        });
+    }
+
+    @Test
+    public void testNoAutofillSaveRequiredOnly() throws Exception {
+        noAutofillSaveOnChangeTest(() -> {
+            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
+            mActivity.mCity.setText("Springfield"); // required
+        }, (s) -> {
+            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1), "742 Evergreen Terrace");
+            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "");
+            assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
+            assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "");
+        });
+    }
+
+    /**
+     * Tests the scenario where the service didn't have any data to autofill, and the user filled
+     * all fields, even the favorite color (LOL).
+     */
+    private void noAutofillSaveOnChangeTest(Runnable changes, Visitor<AssistStructure> assertions)
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(newResponseBuilder().build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started.
+        sReplier.getNextFillRequest();
+
+        // Manually fill fields...
+        mActivity.syncRunOnUiThread(changes);
+
+        // ...then tap save.
+        mActivity.save();
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
+
+        // Assert value of fields
+        assertions.visit(saveRequest.structure);
+    }
+
+    @Test
+    public void testNoAutofillFirstRequiredFieldMissing() throws Exception {
+        noAutofillNoChangeNoSaveTest(() -> {
+            // address1 is missing
+            mActivity.mAddress2.setText("Simpsons House"); // not required
+            mActivity.mCity.setText("Springfield"); // required
+            mActivity.mFavoriteColor.setText("Yellow"); // lol
+        });
+    }
+
+    @Test
+    public void testNoAutofillSecondRequiredFieldMissing() throws Exception {
+        noAutofillNoChangeNoSaveTest(() -> {
+            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
+            mActivity.mAddress2.setText("Simpsons House"); // not required
+            // city is missing
+            mActivity.mFavoriteColor.setText("Yellow"); // lol
+        });
+    }
+
+    /**
+     * Tests the scenario where the service didn't have any data to autofill, and the user filled
+     * didn't fill all required changes.
+     */
+    private void noAutofillNoChangeNoSaveTest(Runnable changes) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(newResponseBuilder().build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started.
+        sReplier.getNextFillRequest();
+
+        // Manually fill fields...
+        mActivity.syncRunOnUiThread(changes);
+
+        // ...then tap save.
+        mActivity.save();
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+    }
+
+    @Test
+    public void testAutofillAllChangedAllSaveAll() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
+                // Initial dataset
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"),
+                // Changes
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
+                    mActivity.mAddress2.setText("Simpsons House"); // not required
+                    mActivity.mCity.setText("Springfield"); // required
+                    mActivity.mFavoriteColor.setText("Yellow"); // lol
+                }, (s) -> {
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
+                            "742 Evergreen Terrace");
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
+                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
+                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Yellow");
+                });
+    }
+
+    @Test
+    public void testAutofillAllChangedFirstRequiredSaveAll() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
+                // Initial dataset
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"),
+                // Changes
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
+                },
+                // Final state
+                (s) -> {
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
+                            "742 Evergreen Terrace");
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Shelbyville Bluffs");
+                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Shelbyville");
+                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
+                });
+    }
+
+    @Test
+    public void testAutofillAllChangedSecondRequiredSaveAll() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
+                // Initial dataset
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"),
+                // Changes
+                () -> {
+                    mActivity.mCity.setText("Springfield"); // required
+                },
+                // Final state
+                (s) -> {
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
+                            "Shelbyville Nuclear Power Plant");
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Shelbyville Bluffs");
+                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
+                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
+                });
+    }
+
+    @Test
+    public void testAutofillAllChangedOptionalSaveAll() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
+                // Initial dataset
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"),
+                // Changes
+                () -> {
+                    mActivity.mAddress2.setText("Simpsons House"); // not required
+                },
+                // Final state
+                (s) -> {
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
+                            "Shelbyville Nuclear Power Plant");
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
+                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Shelbyville");
+                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
+                });
+    }
+
+    /**
+     * Tests the scenario where the service autofilled the activity but the user changed fields
+     * that triggered Save.
+     */
+    private void autofillAndSaveOnChangeTest(CannedDataset.Builder dataset, Runnable changes,
+            Visitor<AssistStructure> assertions) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(newResponseBuilder()
+                .addDataset(dataset.setPresentation(createPresentation("Da Dataset")).build())
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mAddress1.requestFocus();
+        });
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started.
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Da Dataset");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Manually fill fields...
+        mActivity.syncRunOnUiThread(changes);
+
+        // ...then tap save.
+        mActivity.save();
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
+
+        // Assert value of fields
+        assertions.visit(saveRequest.structure);
+    }
+
+    @Test
+    public void testAutofillAllChangedIgnored() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
+                    mActivity.mFavoriteColor.setText("Yellow"); // lol
+                });
+    }
+
+    @Test
+    public void testAutofillAllFirstRequiredChangedToEmpty() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
+                    mActivity.mAddress1.setText("");
+                });
+    }
+
+    @Test
+    public void testAutofillAllSecondRequiredChangedToNull() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
+                    mActivity.mCity.setText(null);
+                });
+    }
+
+    @Test
+    public void testAutofillAllFirstRequiredChangedBackToInitialState() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
+                    mActivity.mAddress1.setText("I'm different");
+                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
+                });
+    }
+
+    /**
+     * Tests the scenario where the service autofilled the activity and the user changed fields,
+     * but it did not triggered Save.
+     */
+    private void autofillNoChangeNoSaveTest(CannedDataset.Builder dataset, Runnable changes)
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(newResponseBuilder()
+                .addDataset(dataset.setPresentation(createPresentation("Da Dataset")).build())
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started.
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Da Dataset");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Manually fill fields...
+        mActivity.syncRunOnUiThread(changes);
+
+        // ...then tap save.
+        mActivity.save();
+
+        // Assert the snack bar is not shown.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_oneDatasetAllRequiredFields()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1, ID_ADDRESS2},
+                null,
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace");
+                    mActivity.mAddress2.setText("Simpsons House");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenManuallyFilledSameValue_oneDatasetRequiredAndOptionalFields()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[]{ID_ADDRESS1},
+                new String[]{ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace");
+                    mActivity.mAddress2.setText("Simpsons House");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                        .setPresentation(createPresentation("SF"))
+                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                        .setField(ID_ADDRESS2, "Simpsons House")
+                        .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_multipleDatasetsDataOnFirst()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace");
+                    mActivity.mAddress2.setText("Simpsons House");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build(),
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SV"))
+                    .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                    .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_multipleDatasetsDataOnSecond()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
+                    mActivity.mAddress2.setText("Shelbyville Bluffs");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build(),
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SV"))
+                    .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                    .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_requiredOnly()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_optionalOnly()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress2.setText("Simpsons House");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_optionalsOnlyNoRequired()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                null,
+                new String[] {ID_ADDRESS2, ID_CITY},
+                () -> {
+                    mActivity.mCity.setText("Springfield");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .setField(ID_CITY, "Springfield")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testShowSaveUiWhenUserManuallyFilledDifferentValue_requiredOnly()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
+                },
+                EXPECT_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testShowSaveUiWhenUserManuallyFilledDifferentValue_optionalOnly()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                null,
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress2.setText("Shelbyville Bluffs");
+                },
+                EXPECT_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    private void saveWhenUserFilledDatasetFields(@Nullable String[] requiredIds,
+            @Nullable String[] optionalIds, @NonNull Runnable changes, boolean expectSaveUi,
+            @NonNull CannedDataset...datasets) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, requiredIds);
+        if (optionalIds != null) {
+            response.setOptionalSavableIds(optionalIds);
+        }
+        for (CannedDataset dataset : datasets) {
+            response.addDataset(dataset);
+        }
+        sReplier.addResponse(response.build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Manually fill it.
+        mActivity.syncRunOnUiThread(changes);
+
+        // ...then tap save.
+        mActivity.save();
+
+        // Make sure the snack bar is shown as expected.
+        if (expectSaveUi) {
+            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_ADDRESS);
+        } else {
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+        }
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserClearedAutofilledFieldThatIsRequired() throws Exception {
+        // Set service.
+        enableService();
+
+        mActivity.expectAutoFill("742 Evergreen Terrace", "Simpsons House",
+                "Springfield", "Yellow");
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
+                .setOptionalSavableIds(ID_CITY)
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("SF"))
+                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                        .setField(ID_ADDRESS2, "Simpsons House")
+                        .setField(ID_CITY, "Springfield")
+                        .setField(ID_FAVORITE_COLOR, "Yellow")
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mUiBot.selectDataset("SF");
+        mActivity.assertAutoFilled();
+
+        // Clear the field.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress2.setText(""));
+
+        // Trigger save...
+        mActivity.save();
+
+        // ...and make sure the snack bar is not shown.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+    }
+
+    @Test
+    public void testShowSaveUiWhenUserClearedAutofilledFieldThatIsOptional() throws Exception {
+        // Set service.
+        enableService();
+
+        mActivity.expectAutoFill("742 Evergreen Terrace", "Simpsons House",
+                "Springfield", "Yellow");
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
+                .setOptionalSavableIds(ID_CITY)
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("SF"))
+                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                        .setField(ID_ADDRESS2, "Simpsons House")
+                        .setField(ID_CITY, "Springfield")
+                        .setField(ID_FAVORITE_COLOR, "Yellow")
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mUiBot.selectDataset("SF");
+        mActivity.assertAutoFilled();
+
+        // Clear the field.
+        mActivity.syncRunOnUiThread(() -> mActivity.mCity.setText(""));
+
+        // Trigger save...
+        mActivity.save();
+
+        // ...and make sure the snack bar is shown.
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+
+        // Finally, assert values.
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS1),
+                "742 Evergreen Terrace");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS2),
+                "Simpsons House");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CITY), "");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_FAVORITE_COLOR),
+                "Yellow");
+    }
+
+    @Test
+    public void testShowUpdateWhenUserChangedOptionalValueFromDatasetAndRequiredNotFromDataset()
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Address 2 will be required but not available
+        mActivity.expectAutoFill("742 Evergreen Terrace", null, "Springfield", "Yellow");
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
+                .setOptionalSavableIds(ID_CITY)
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("SF"))
+                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                        .setField(ID_CITY, "Springfield")
+                        .setField(ID_FAVORITE_COLOR, "Yellow")
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mUiBot.selectDataset("SF");
+        mActivity.assertAutoFilled();
+
+        // Change required and optional field.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mAddress2.setText("Simpsons House");
+            mActivity.mCity.setText("Shelbyville");
+        });
+        // Trigger save...
+        mActivity.save();
+
+        // ...and make sure the snack bar is shown.
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+
+        // Finally, assert values.
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS1),
+                "742 Evergreen Terrace");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS2),
+                "Simpsons House");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CITY), "Shelbyville");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_FAVORITE_COLOR),
+                "Yellow");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java
new file mode 100644
index 0000000..f7a9030
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.activities.LoginActivity.ID_USERNAME_CONTAINER;
+import static android.autofillservice.cts.activities.PreSimpleSaveActivity.ID_PRE_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_STATIC_TEXT;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.PreSimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.activities.TrampolineWelcomeActivity;
+import android.autofillservice.cts.activities.WelcomeActivity;
+import android.autofillservice.cts.commontests.CustomDescriptionWithLinkTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.UiBot;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.Validator;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import java.util.regex.Pattern;
+
+public class PreSimpleSaveActivityTest
+        extends CustomDescriptionWithLinkTestCase<PreSimpleSaveActivity> {
+
+    private static final AutofillActivityTestRule<PreSimpleSaveActivity> sActivityRule =
+            new AutofillActivityTestRule<PreSimpleSaveActivity>(PreSimpleSaveActivity.class, false);
+
+    public PreSimpleSaveActivityTest() {
+        super(PreSimpleSaveActivity.class);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<PreSimpleSaveActivity> getActivityRule() {
+        return sActivityRule;
+    }
+
+    @Override
+    protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception {
+        startActivity(false);
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPreInput.setText("108");
+            mActivity.mSubmit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Tap the link.
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // .. then do something to return to previous activity...
+        switch (type) {
+            case ROTATE_THEN_TAP_BACK_BUTTON:
+                mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+                WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+                // not breaking on purpose
+            case TAP_BACK_BUTTON:
+                mUiBot.pressBack();
+                break;
+            case FINISH_ACTIVITY:
+                // ..then finishes it.
+                WelcomeActivity.finishIt();
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + type);
+        }
+
+        // ... and tap save.
+        final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(newSaveUi, true);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT), "108");
+    }
+
+    @Override
+    protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
+            boolean manualRequest) throws Exception {
+        startActivity(false);
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPreInput.setText("108");
+            mActivity.mSubmit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Tap the link.
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Tap back to restore the Save UI...
+        mUiBot.pressBack();
+
+        // ...but don't tap it...
+        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...instead, do something to dismiss it:
+        switch (action) {
+            case TOUCH_OUTSIDE:
+                mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
+                break;
+            case TAP_NO_ON_SAVE_UI:
+                mUiBot.saveForAutofill(saveUi2, false);
+                break;
+            case TAP_YES_ON_SAVE_UI:
+                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+                assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT),
+                        "108");
+                break;
+            default:
+                throw new IllegalArgumentException("invalid action: " + action);
+        }
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Make sure previous session was finished.
+
+        // Now triggers a new session in the new activity (SaveActivity) and do business as usual...
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_EMAIL_ADDRESS, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        final SimpleSaveActivity newActivty = SimpleSaveActivity.getInstance();
+        if (manualRequest) {
+            newActivty.getAutofillManager().requestAutofill(newActivty.mInput);
+        } else {
+            newActivty.syncRunOnUiThread(() -> newActivty.mPassword.requestFocus());
+        }
+
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        newActivty.syncRunOnUiThread(() -> {
+            newActivty.mInput.setText("42");
+            newActivty.mCommit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
+    }
+
+    @Override
+    protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception {
+        startActivity(false);
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPreInput.setText("108");
+            mActivity.mSubmit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Tap the link.
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+        tapSaveUiLink(saveUi);
+
+        // Make sure linked activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        switch (type) {
+            case LAUNCH_PREVIOUS_ACTIVITY:
+                startActivityOnNewTask(PreSimpleSaveActivity.class);
+                mUiBot.assertShownByRelativeId(ID_INPUT);
+                break;
+            case LAUNCH_NEW_ACTIVITY:
+                // Launch a 3rd activity...
+                startActivityOnNewTask(LoginActivity.class);
+                mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+                // ...then go back
+                mUiBot.pressBack();
+                mUiBot.assertShownByRelativeId(ID_INPUT);
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + type);
+        }
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Override
+    protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
+            throws Exception {
+        // Prepare activity.
+        startActivity(false);
+        mActivity.mPreInput.getRootView()
+                .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder.setCustomDescription(
+                        newCustomDescription(TrampolineWelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.getAutofillManager().requestAutofill(mActivity.mPreInput);
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPreInput.setText("108");
+            mActivity.mSubmit.performClick();
+        });
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+
+        // Save UI should be showing as well, since Trampoline finished.
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Go back and make sure it's showing the right activity.
+        // first BACK cancels save dialog
+        mUiBot.pressBack();
+        // second BACK cancel WelcomeActivity
+        mUiBot.pressBack();
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Now triggers a new session in the new activity (SaveActivity) and do business as usual...
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_EMAIL_ADDRESS, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        final SimpleSaveActivity newActivty = SimpleSaveActivity.getInstance();
+        newActivty.getAutofillManager().requestAutofill(newActivty.mInput);
+
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        newActivty.syncRunOnUiThread(() -> {
+            newActivty.mInput.setText("42");
+            newActivty.mCommit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
+
+        // ... and assert results
+        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest1.structure, ID_INPUT), "42");
+    }
+
+    @Override
+    protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
+        startActivity(false);
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final CustomDescription.Builder customDescription =
+                            newCustomDescriptionBuilder(WelcomeActivity.class);
+                    final RemoteViews update = newTemplate();
+                    if (updateLinkView) {
+                        update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
+                    } else {
+                        update.setCharSequence(R.id.static_text, "setText", "ME!");
+                    }
+                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_PRE_INPUT);
+                    final Validator validCondition = new RegexValidator(id, Pattern.compile(".*"));
+                    customDescription.batchUpdate(validCondition,
+                            new BatchUpdates.Builder().updateTemplate(update).build());
+                    builder.setCustomDescription(customDescription.build());
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPreInput.setText("108");
+            mActivity.mSubmit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Tap the link.
+        final UiObject2 saveUi;
+        if (updateLinkView) {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD, "TAP ME IF YOU CAN");
+        } else {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+            final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
+            assertThat(changed.getText()).isEqualTo("ME!");
+        }
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java
new file mode 100644
index 0000000..554030f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java
@@ -0,0 +1,1899 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.activities.LoginActivity.ID_USERNAME_CONTAINER;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_COMMIT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_LABEL;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_PASSWORD;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.TEXT_LABEL;
+import static android.autofillservice.cts.testcore.AntiTrimmerTextWatcher.TRIMMER_PATTERN;
+import static android.autofillservice.cts.testcore.Helper.ID_STATIC_TEXT;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.LARGE_STRING;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextValue;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.SecondActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity.FillExpectation;
+import android.autofillservice.cts.activities.TrampolineWelcomeActivity;
+import android.autofillservice.cts.activities.ViewActionActivity;
+import android.autofillservice.cts.activities.WelcomeActivity;
+import android.autofillservice.cts.commontests.CustomDescriptionWithLinkTestCase;
+import android.autofillservice.cts.testcore.AntiTrimmerTextWatcher;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.DismissType;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.MyAutofillId;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.TextValueSanitizer;
+import android.service.autofill.Validator;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.URLSpan;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+import java.util.regex.Pattern;
+
+public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase<SimpleSaveActivity> {
+
+    private static final AutofillActivityTestRule<SimpleSaveActivity> sActivityRule =
+            new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class, false);
+
+    private static final AutofillActivityTestRule<WelcomeActivity> sWelcomeActivityRule =
+            new AutofillActivityTestRule<WelcomeActivity>(WelcomeActivity.class, false);
+
+    public SimpleSaveActivityTest() {
+        super(SimpleSaveActivity.class);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
+        return sActivityRule;
+    }
+
+    @Override
+    protected TestRule getMainTestRule() {
+        return RuleChain.outerRule(sActivityRule).around(sWelcomeActivityRule);
+    }
+
+    private void restartActivity() {
+        final Intent intent = new Intent(mContext.getApplicationContext(),
+                SimpleSaveActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+        mActivity.startActivity(intent);
+    }
+
+    @Test
+    public void testAutoFillOneDatasetAndSave() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Select dataset.
+        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+        mUiBot.selectDataset("YO");
+        autofillExpecation.assertAutoFilled();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("ID");
+            mActivity.mPassword.setText("PASS");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testAutoFillOneDatasetAndSave_largeAssistStructure() throws Exception {
+        startActivity();
+
+        mActivity.syncRunOnUiThread(
+                () -> mActivity.mInput.setAutofillHints(LARGE_STRING, LARGE_STRING, LARGE_STRING));
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        final ViewNode inputOnFill = findNodeByResourceId(fillRequest.structure, ID_INPUT);
+        final String[] hintsOnFill = inputOnFill.getAutofillHints();
+        // Cannot compare these large strings directly becauise it could cause ANR
+        assertThat(hintsOnFill).hasLength(3);
+        Helper.assertEqualsToLargeString(hintsOnFill[0]);
+        Helper.assertEqualsToLargeString(hintsOnFill[1]);
+        Helper.assertEqualsToLargeString(hintsOnFill[2]);
+
+        // Select dataset.
+        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+        mUiBot.selectDataset("YO");
+        autofillExpecation.assertAutoFilled();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("ID");
+            mActivity.mPassword.setText("PASS");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode inputOnSave = findNodeByResourceId(saveRequest.structure, ID_INPUT);
+        assertTextAndValue(inputOnSave, "ID");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
+
+        final String[] hintsOnSave = inputOnSave.getAutofillHints();
+        // Cannot compare these large strings directly becauise it could cause ANR
+        assertThat(hintsOnSave).hasLength(3);
+        Helper.assertEqualsToLargeString(hintsOnSave[0]);
+        Helper.assertEqualsToLargeString(hintsOnSave[1]);
+        Helper.assertEqualsToLargeString(hintsOnSave[2]);
+    }
+
+    /**
+     * Simple test that only uses UiAutomator to interact with the activity, so it indirectly
+     * tests the integration of Autofill with Accessibility.
+     */
+    @Test
+    public void testAutoFillOneDatasetAndSave_usingUiAutomatorOnly() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mUiBot.assertShownByRelativeId(ID_INPUT).click();
+        sReplier.getNextFillRequest();
+
+        // Select dataset...
+        mUiBot.selectDataset("YO");
+
+        // ...and assert autofilled values.
+        final UiObject2 input = mUiBot.assertShownByRelativeId(ID_INPUT);
+        final UiObject2 password = mUiBot.assertShownByRelativeId(ID_PASSWORD);
+
+        assertWithMessage("wrong value for 'input'").that(input.getText()).isEqualTo("id");
+        // TODO: password field is shown as **** ; ideally we should assert it's a password
+        // field, but UiAutomator does not exposes that info.
+        final String visiblePassword = password.getText();
+        assertWithMessage("'password' should not be visible").that(visiblePassword)
+            .isNotEqualTo("pass");
+        assertWithMessage("wrong value for 'password'").that(visiblePassword).hasLength(4);
+
+        // Trigger save...
+        input.setText("ID");
+        password.setText("PASS");
+        mUiBot.assertShownByRelativeId(ID_COMMIT).click();
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSave() throws Exception {
+        saveTest(false);
+    }
+
+    @Test
+    public void testSave_afterRotation() throws Exception {
+        assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
+        mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+        try {
+            saveTest(true);
+        } finally {
+            try {
+                mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+                cleanUpAfterScreenOrientationIsBackToPortrait();
+            } catch (Exception e) {
+                mSafeCleanerRule.add(e);
+            }
+        }
+    }
+
+    private void saveTest(boolean rotate) throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        if (rotate) {
+            // After the device rotates, the input field get focus and generate a new session.
+            sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+            mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+            saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        }
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+    }
+
+    /**
+     * Emulates an app dyanmically adding the password field after username is typed.
+     */
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testPartitionedSave() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // 1st request
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Set 1st field but don't commit session
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
+        mUiBot.assertSaveNotShowing();
+
+        // 2nd request
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        ID_INPUT, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPassword.setText("42");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(null, SAVE_DATA_TYPE_USERNAME,
+                SAVE_DATA_TYPE_PASSWORD);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
+    }
+
+    /**
+     * Emulates an app using fragments to display username and password in 2 steps.
+     */
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testDelayedSave() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // 1st fragment.
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE).build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger delayed save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        mUiBot.assertSaveNotShowing();
+
+        // 2nd fragment.
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                // Must explicitly set visitor, otherwise setRequiredSavableIds() would get the
+                // id from the 1st context
+                .setVisitor((contexts, builder) -> {
+                    final AutofillId passwordId =
+                            findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD);
+                    final AutofillId inputId =
+                            findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
+                    builder.setSaveInfo(new SaveInfo.Builder(
+                            SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                            new AutofillId[] {inputId, passwordId})
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill on second "fragment"
+        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger delayed save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPassword.setText("42");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        // Get username from 1st request.
+        final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure();
+        assertTextAndValue(findNodeByResourceId(structure1, ID_INPUT), "108");
+
+        // Get password from 2nd request.
+        final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure();
+        assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "108");
+        assertTextAndValue(findNodeByResourceId(structure2, ID_PASSWORD), "42");
+    }
+
+    @Test
+    public void testSave_launchIntent() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.setOnSave(WelcomeActivity.createSender(mContext, "Saved by the bell"))
+                .addResponse(new CannedFillResponse.Builder()
+                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                        .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+
+            // Disable autofill so it's not triggered again after WelcomeActivity finishes
+            // and mActivity is resumed (with focus on mInput) after the session is closed
+            mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        sReplier.getNextSaveRequest();
+
+        // ... and assert activity was launched
+        WelcomeActivity.assertShowing(mUiBot, "Saved by the bell");
+    }
+
+    @Test
+    public void testSaveThenStartNewSessionRightAwayShouldKeepSaveUi() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+
+        // Make sure Save UI for 1st session was shown....
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Start new Activity to have a new autofill session
+        startActivityOnNewTask(LoginActivity.class);
+
+        // Make sure LoginActivity started...
+        mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "id")
+                        .setField(ID_PASSWORD, "pwd")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+        // Trigger fill request on the LoginActivity
+        final LoginActivity act = LoginActivity.getCurrentActivity();
+        act.syncRunOnUiThread(() -> act.forceAutofillOnUsername());
+        sReplier.getNextFillRequest();
+
+        // Make sure Fill UI is not shown. And Save UI for 1st session was still shown.
+        mUiBot.assertNoDatasetsEver();
+        sReplier.assertNoUnhandledFillRequests();
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        mUiBot.waitForIdle();
+        // Trigger dismiss Save UI
+        mUiBot.pressBack();
+
+        // Make sure Save UI was not shown....
+        mUiBot.assertSaveNotShowing();
+        // Make sure Fill UI is shown.
+        mUiBot.assertDatasets("YO");
+    }
+
+    @Test
+    public void testCloseSaveUiThenStartNewSessionRightAway() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+
+        // Make sure Save UI for 1st session was shown....
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Trigger dismiss Save UI
+        mUiBot.pressBack();
+
+        // Make sure Save UI for 1st session was canceled.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // ...then start the new session right away (without finishing the activity).
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("");
+            mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
+        });
+        sReplier.getNextFillRequest();
+
+        // Make sure Fill UI is shown.
+        mUiBot.assertDatasets("YO");
+    }
+
+    @Test
+    public void testSaveWithParcelableOnClientState() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final AutofillId id = new AutofillId(42);
+        final Bundle clientState = new Bundle();
+        clientState.putParcelable("id", id);
+        clientState.putParcelable("my_id", new MyAutofillId(id));
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setExtras(clientState)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertMyClientState(saveRequest.data);
+
+        // Also check fillevent history
+        final FillEventHistory history = InstrumentedAutoFillService.getFillEventHistory(1);
+        @SuppressWarnings("deprecation")
+        final Bundle deprecatedState = history.getClientState();
+        assertMyClientState(deprecatedState);
+        assertMyClientState(history.getEvents().get(0).getClientState());
+    }
+
+    private void assertMyClientState(Bundle data) {
+        // Must set proper classpath before reading the data, otherwise Bundle will use it's
+        // on class classloader, which is the framework's.
+        data.setClassLoader(getClass().getClassLoader());
+
+        final AutofillId expectedId = new AutofillId(42);
+        final AutofillId actualId = data.getParcelable("id");
+        assertThat(actualId).isEqualTo(expectedId);
+        final MyAutofillId actualMyId = data.getParcelable("my_id");
+        assertThat(actualMyId).isEqualTo(new MyAutofillId(expectedId));
+    }
+
+    @Test
+    public void testCancelPreventsSaveUiFromShowing() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Cancel session.
+        mActivity.getAutofillManager().cancel();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    public void testDismissSave_byTappingBack() throws Exception {
+        startActivity();
+        dismissSaveTest(DismissType.BACK_BUTTON);
+    }
+
+    @Test
+    public void testDismissSave_byTappingHome() throws Exception {
+        startActivity();
+        dismissSaveTest(DismissType.HOME_BUTTON);
+    }
+
+    @Test
+    public void testDismissSave_byTouchingOutside() throws Exception {
+        startActivity();
+        dismissSaveTest(DismissType.TOUCH_OUTSIDE);
+    }
+
+    @Test
+    public void testDismissSave_byFocusingOutside() throws Exception {
+        startActivity();
+        dismissSaveTest(DismissType.FOCUS_OUTSIDE);
+    }
+
+    private void dismissSaveTest(DismissType dismissType) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Then make sure it goes away when user doesn't want it..
+        switch (dismissType) {
+            case BACK_BUTTON:
+                mUiBot.pressBack();
+                break;
+            case HOME_BUTTON:
+                mUiBot.pressHome();
+                break;
+            case TOUCH_OUTSIDE:
+                mUiBot.assertShownByText(TEXT_LABEL).click();
+                break;
+            case FOCUS_OUTSIDE:
+                mActivity.syncRunOnUiThread(() -> mActivity.mLabel.requestFocus());
+                mUiBot.assertShownByText(TEXT_LABEL).click();
+                break;
+            default:
+                throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
+        }
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    public void testTapHomeWhileDatasetPickerUiIsShowing() throws Exception {
+        startActivity();
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mUiBot.assertShownByRelativeId(ID_INPUT).click();
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("YO");
+        callback.assertUiShownEvent(mActivity.mInput);
+
+        // Go home, you are drunk!
+        mUiBot.pressHome();
+        mUiBot.assertNoDatasets();
+        callback.assertUiHiddenEvent(mActivity.mInput);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO2"))
+                        .build())
+                .build());
+
+        // Switch back to the activity.
+        restartActivity();
+        mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
+        sReplier.getNextFillRequest();
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("YO2");
+        callback.assertUiShownEvent(mActivity.mInput);
+
+        // Now autofill it.
+        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+        mUiBot.selectDataset(datasetPicker, "YO2");
+        autofillExpecation.assertAutoFilled();
+    }
+
+    @Test
+    public void testTapHomeWhileSaveUiIsShowing() throws Exception {
+        startActivity();
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save, but don't tap it.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Go home, you are drunk!
+        mUiBot.pressHome();
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Prepare the response for the next session, which will be automatically triggered
+        // when the activity is brought back.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Switch back to the activity.
+        restartActivity();
+        mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger and select UI.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+        mUiBot.selectDataset("YO");
+
+        // Assert it.
+        autofillExpecation.assertAutoFilled();
+    }
+
+    @Override
+    protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception {
+        startActivity();
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // .. then do something to return to previous activity...
+        switch (type) {
+            case ROTATE_THEN_TAP_BACK_BUTTON:
+                // After the device rotates, the input field get focus and generate a new session.
+                sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+                mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+                WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+                // not breaking on purpose
+            case TAP_BACK_BUTTON:
+                // ..then go back and save it.
+                mUiBot.pressBack();
+                break;
+            case FINISH_ACTIVITY:
+                // ..then finishes it.
+                WelcomeActivity.finishIt();
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + type);
+        }
+        // Make sure previous activity is back...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // ... and tap save.
+        final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(newSaveUi, true);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+    }
+
+    @Override
+    protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
+        sReplier.getNextFillRequest();
+    }
+
+    @Override
+    protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
+            boolean manualRequest) throws Exception {
+        startActivity();
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown.
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Tap back to restore the Save UI...
+        mUiBot.pressBack();
+        // Make sure previous activity is back...
+        mUiBot.assertShownByRelativeId(ID_LABEL);
+
+        // ...but don't tap it...
+        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // ...instead, do something to dismiss it:
+        switch (action) {
+            case TOUCH_OUTSIDE:
+                mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
+                break;
+            case TAP_NO_ON_SAVE_UI:
+                mUiBot.saveForAutofill(saveUi2, false);
+                break;
+            case TAP_YES_ON_SAVE_UI:
+                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+                assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+                break;
+            default:
+                throw new IllegalArgumentException("invalid action: " + action);
+        }
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Now triggers a new session and do business as usual...
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        if (manualRequest) {
+            mActivity.syncRunOnUiThread(
+                    () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput));
+        } else {
+            mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        }
+
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("42");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
+    }
+
+    @Override
+    protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception {
+        startActivity(false);
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        switch (type) {
+            case LAUNCH_PREVIOUS_ACTIVITY:
+                startActivityOnNewTask(SimpleSaveActivity.class);
+                break;
+            case LAUNCH_NEW_ACTIVITY:
+                // Launch a 3rd activity...
+                startActivityOnNewTask(LoginActivity.class);
+                mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+                // ...then go back
+                mUiBot.pressBack();
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + type);
+        }
+        // Make sure right activity is showing
+        mUiBot.assertShownByRelativeId(ID_INPUT, Timeouts.ACTIVITY_RESURRECTION);
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testSelectedDatasetsAreSentOnSaveRequest() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                // Added on reversed order on purpose
+                .addDataset(new CannedDataset.Builder()
+                        .setId("D2")
+                        .setField(ID_INPUT, "id again")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("D2"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("D1")
+                        .setField(ID_INPUT, "id")
+                        .setPresentation(createPresentation("D1"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Select 1st dataset.
+        final FillExpectation autofillExpecation1 = mActivity.expectAutoFill("id");
+        final UiObject2 picker1 = mUiBot.assertDatasets("D2", "D1");
+        mUiBot.selectDataset(picker1, "D1");
+        autofillExpecation1.assertAutoFilled();
+
+        // Select 2nd dataset.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        final FillExpectation autofillExpecation2 = mActivity.expectAutoFill("id again", "pass");
+        final UiObject2 picker2 = mUiBot.assertDatasets("D2");
+        mUiBot.selectDataset(picker2, "D2");
+        autofillExpecation2.assertAutoFilled();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("ID");
+            mActivity.mPassword.setText("PASS");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
+        assertThat(saveRequest.datasetIds).containsExactly("D1", "D2").inOrder();
+    }
+
+    @Override
+    protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
+            throws Exception {
+        // Prepare activity.
+        startActivity();
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.getRootView()
+                .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS)
+        );
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(
+                                newCustomDescription(TrampolineWelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(
+                () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput));
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+
+        // Save UI should be showing as well, since Trampoline finished.
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Dismiss Save Dialog
+        mUiBot.pressBack();
+        // Go back and make sure it's showing the right activity.
+        mUiBot.pressBack();
+        mUiBot.assertShownByRelativeId(ID_LABEL);
+
+        // Now start a new session.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill on password
+        mActivity.syncRunOnUiThread(
+                () -> mActivity.getAutofillManager().requestAutofill(mActivity.mPassword));
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPassword.setText("42");
+            mActivity.mCommit.performClick();
+        });
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
+    }
+
+    @Test
+    public void testSanitizeOnSaveWhenAppChangeValues() throws Exception {
+        startActivity();
+
+        // Set listeners that will change the saved value
+        new AntiTrimmerTextWatcher(mActivity.mInput);
+        new AntiTrimmerTextWatcher(mActivity.mPassword);
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
+                            passwordId);
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testSanitizeOnSaveNoChange() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setOptionalSavableIds(ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
+                            passwordId);
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("#id#");
+            mActivity.mPassword.setText("#pass#");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testDontSaveWhenSanitizedValueForRequiredFieldDidntChange() throws Exception {
+        startActivity();
+
+        // Set listeners that will change the saved value
+        new AntiTrimmerTextWatcher(mActivity.mInput);
+        new AntiTrimmerTextWatcher(mActivity.mPassword);
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
+                            passwordId);
+                })
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testDontSaveWhenSanitizedValueForOptionalFieldDidntChange() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setOptionalSavableIds(ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("(pass) "), "$1"),
+                            passwordId);
+                })
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("#pass#");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testDontSaveWhenRequiredFieldFailedSanitization() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
+                            inputId, passwordId);
+                })
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "#id#")
+                        .setField(ID_PASSWORD, "#pass#")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testDontSaveWhenOptionalFieldFailedSanitization() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setOptionalSavableIds(ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
+                            inputId, passwordId);
+
+                })
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "#pass#")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testDontSaveWhenInitialValueAndNoUserInputAndServiceDatasets() throws Throwable {
+        // Prepare activitiy.
+        startActivity();
+        mActivity.syncRunOnUiThread(() -> {
+            // NOTE: input's value must be a subset of the dataset value, otherwise the dataset
+            // picker is filtered out
+            mActivity.mInput.setText("f");
+            mActivity.mPassword.setText("b");
+        });
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "foo")
+                        .setField(ID_PASSWORD, "bar")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Trigger save.
+        mActivity.getAutofillManager().commit();
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    enum SetTextCondition {
+        NORMAL,
+        HAS_SESSION,
+        EMPTY_TEXT,
+        FOCUSED,
+        NOT_IMPORTANT_FOR_AUTOFILL,
+        INVISIBLE
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should trigger autofill and
+     * show Save UI.
+     */
+    @Test
+    public void testShowSaveUiWhenSetTextAutomatically() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NORMAL);
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+     * when there is an existing session.
+     */
+    @Test
+    public void testNotTriggerAutofillWhenSetTextWhileSessionExists() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.HAS_SESSION);
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+     * when the text is empty.
+     */
+    @Test
+    public void testNotTriggerAutofillWhenSetTextWhileEmptyText() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.EMPTY_TEXT);
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+     * when the field is focused.
+     */
+    @Test
+    public void testNotTriggerAutofillWhenSetTextWhileFocused() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.FOCUSED);
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+     * when the field is not important for autofill.
+     */
+    @Test
+    public void testNotTriggerAutofillWhenSetTextWhileNotImportantForAutofill() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NOT_IMPORTANT_FOR_AUTOFILL);
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+     * when the field is not visible.
+     */
+    @Test
+    public void testNotTriggerAutofillWhenSetTextWhileInvisible() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.INVISIBLE);
+    }
+
+    private void triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition condition)
+            throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        CharSequence inputText = "108";
+
+        switch (condition) {
+            case NORMAL:
+                // Nothing.
+                break;
+            case HAS_SESSION:
+                mActivity.syncRunOnUiThread(() -> {
+                    mActivity.mInput.setText("100");
+                });
+                sReplier.getNextFillRequest();
+                break;
+            case EMPTY_TEXT:
+                inputText = "";
+                break;
+            case FOCUSED:
+                mActivity.syncRunOnUiThread(() -> {
+                    mActivity.mInput.requestFocus();
+                });
+                sReplier.getNextFillRequest();
+                break;
+            case NOT_IMPORTANT_FOR_AUTOFILL:
+                mActivity.syncRunOnUiThread(() -> {
+                    mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
+                });
+                break;
+            case INVISIBLE:
+                mActivity.syncRunOnUiThread(() -> {
+                    mActivity.mInput.setVisibility(View.INVISIBLE);
+                });
+                break;
+            default:
+                throw new IllegalArgumentException("invalid condition: " + condition);
+        }
+
+        // Trigger autofill by setting text.
+        final CharSequence text = inputText;
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText(text);
+        });
+
+        if (condition == SetTextCondition.NORMAL) {
+            sReplier.getNextFillRequest();
+
+            mActivity.syncRunOnUiThread(() -> {
+                mActivity.mInput.setText("100");
+                mActivity.mCommit.performClick();
+            });
+
+            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        } else {
+            sReplier.assertOnFillRequestNotCalled();
+        }
+    }
+
+    @Test
+    public void testExplicitlySaveButton() throws Exception {
+        explicitlySaveButtonTest(false, 0);
+    }
+
+    @Test
+    public void testExplicitlySaveButtonWhenAppClearFields() throws Exception {
+        explicitlySaveButtonTest(true, 0);
+    }
+
+    @Test
+    public void testExplicitlySaveButtonOnly() throws Exception {
+        explicitlySaveButtonTest(false, SaveInfo.FLAG_DONT_SAVE_ON_FINISH);
+    }
+
+    /**
+     * Tests scenario where service explicitly indicates which button is used to save.
+     */
+    private void explicitlySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception {
+        final boolean testBitmap = false;
+        startActivity();
+        mActivity.setAutoCommit(false);
+        mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit);
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveTriggerId(mActivity.mCommit.getAutofillId())
+                .setSaveInfoFlags(flags)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
+
+        // Take a screenshot to make sure button doesn't disappear.
+        final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
+        assertThat(commitBefore.toUpperCase()).isEqualTo("COMMIT");
+        // Disable unnecessary screenshot tests as takeScreenshot() fails on some device.
+
+        final Bitmap screenshotBefore = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
+                : null;
+
+        // Save it...
+        mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // Make sure save button is showning (it was removed on earlier versions of the feature)
+        final String commitAfter = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
+        assertThat(commitAfter.toUpperCase()).isEqualTo("COMMIT");
+        final Bitmap screenshotAfter = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
+                : null;
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+
+        if (testBitmap) {
+            Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter);
+        }
+    }
+
+    @Override
+    protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
+        startActivity();
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    // Set response with custom description
+                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
+                    final CustomDescription.Builder customDescription =
+                            newCustomDescriptionBuilder(WelcomeActivity.class);
+                    final RemoteViews update = newTemplate();
+                    if (updateLinkView) {
+                        update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
+                    } else {
+                        update.setCharSequence(R.id.static_text, "setText", "ME!");
+                    }
+                    Validator validCondition = new RegexValidator(id, Pattern.compile(".*"));
+                    customDescription.batchUpdate(validCondition,
+                            new BatchUpdates.Builder().updateTemplate(update).build());
+
+                    builder.setCustomDescription(customDescription.build());
+                })
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi;
+        if (updateLinkView) {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC, "TAP ME IF YOU CAN");
+        } else {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+            final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
+            assertThat(changed.getText()).isEqualTo("ME!");
+        }
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    enum DescriptionType {
+        SUCCINCT,
+        CUSTOM,
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the custom description, then the new activity
+     * finishes:
+     * the Save UI should have been restored.
+     */
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnCustomDescription_thenTapBack() throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
+                ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the succinct description, then the new activity
+     * finishes:
+     * the Save UI should have been restored.
+     */
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnSuccinctDescription_thenTapBack() throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
+                ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the custom description, then the new activity
+     * starts an another activity then it finishes:
+     * the Save UI should have been restored.
+     */
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnCustomDescription_forwardAnotherActivityThenTapBack()
+            throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
+                ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the succinct description, then the new activity
+     * starts an another activity then it finishes:
+     * the Save UI should have been restored.
+     */
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnSuccinctDescription_forwardAnotherActivityThenTapBack()
+            throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
+                ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the custom description, then the new activity
+     * stops but does not finish:
+     * the Save UI should have been restored.
+     */
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnCustomDescription_tapBackWithoutFinish() throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
+                ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the succinct description, then the new activity
+     * stops but does not finish:
+     * the Save UI should have been restored.
+     */
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnSuccinctDescription_tapBackWithoutFinish() throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
+                ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
+    }
+
+    private void saveUiRestoredAfterTappingSpanTest(
+            DescriptionType type, ViewActionActivity.ActivityCustomAction action) throws Exception {
+        startActivity();
+        // Set service.
+        enableService();
+
+        switch (type) {
+            case SUCCINCT:
+                // Set expectations with custom description.
+                sReplier.addResponse(new CannedFillResponse.Builder()
+                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                        .setSaveDescription(newDescriptionWithUrlSpan(action.toString()))
+                        .build());
+                break;
+            case CUSTOM:
+                // Set expectations with custom description.
+                sReplier.addResponse(new CannedFillResponse.Builder()
+                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                        .setSaveInfoVisitor((contexts, builder) -> builder
+                                .setCustomDescription(
+                                        newCustomDescriptionWithUrlSpan(action.toString())))
+                        .build());
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + type);
+        }
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        // Waits for the commit be processed
+        mUiBot.waitForIdle();
+
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Tapping URLSpan.
+        final URLSpan span = mUiBot.findFirstUrlSpanWithText("Here is URLSpan");
+        mActivity.syncRunOnUiThread(() -> span.onClick(/* unused= */ null));
+        // Waits for the save UI hided
+        mUiBot.waitForIdle();
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // .. check activity show up as expected
+        switch (action) {
+            case FAST_FORWARD_ANOTHER_ACTIVITY:
+                // Show up second activity.
+                SecondActivity.assertShowingDefaultMessage(mUiBot);
+                break;
+            case NORMAL_ACTIVITY:
+            case TAP_BACK_WITHOUT_FINISH:
+                // Show up view action handle activity.
+                ViewActionActivity.assertShowingDefaultMessage(mUiBot);
+                break;
+            default:
+                throw new IllegalArgumentException("invalid action: " + action);
+        }
+
+        // ..then go back and save it.
+        mUiBot.pressBack();
+        // Waits for all UI processes to complete
+        mUiBot.waitForIdle();
+
+        // Make sure previous activity is back...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // ... and tap save.
+        final UiObject2 newSaveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(newSaveUi, /* yesDoIt= */ true);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+
+        SecondActivity.finishIt();
+        ViewActionActivity.finishIt();
+    }
+
+    private CustomDescription newCustomDescriptionWithUrlSpan(String action) {
+        final RemoteViews presentation = newTemplate();
+        presentation.setTextViewText(R.id.custom_text, newDescriptionWithUrlSpan(action));
+        return new CustomDescription.Builder(presentation).build();
+    }
+
+    private CharSequence newDescriptionWithUrlSpan(String action) {
+        final String url = "autofillcts:" + action;
+        final SpannableString ss = new SpannableString("Here is URLSpan");
+        ss.setSpan(new URLSpan(url),
+                /* start= */ 8,  /* end= */ 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        return ss;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/DisableAutofillTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/DisableAutofillTest.java
new file mode 100644
index 0000000..4693984
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/DisableAutofillTest.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.servicebehavior;
+
+import static android.autofillservice.cts.testcore.Timeouts.ACTIVITY_RESURRECTION;
+import static android.autofillservice.cts.testcore.Timeouts.CALLBACK_NOT_CALLED_TIMEOUT_MS;
+
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+import android.autofillservice.cts.activities.PreSimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.FillResponse;
+import android.util.Log;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for the {@link android.service.autofill.FillResponse.Builder#disableAutofill(long)} API.
+ */
+public class DisableAutofillTest extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+    private static final String TAG = "DisableAutofillTest";
+
+    /**
+     * Defines what to do after the activity being tested is launched.
+     */
+    enum PostLaunchAction {
+        /**
+         * Used when the service disables autofill in the fill response for this activty. As such:
+         *
+         * <ol>
+         *   <li>There should be a fill request on {@code sReplier}.
+         *   <li>The first UI focus should generate a
+         *   {@link android.view.autofill.AutofillManager.AutofillCallback#EVENT_INPUT_UNAVAILABLE}
+         *   event.
+         *   <li>Subsequent UI focus should not trigger events.
+         * </ol>
+         */
+        ASSERT_DISABLING,
+
+        /**
+         * Used when the service already disabled autofill prior to launching activty. As such:
+         *
+         * <ol>
+         *   <li>There should be no fill request on {@code sReplier}.
+         *   <li>There should be no callback calls when UI is focused
+         * </ol>
+         */
+        ASSERT_DISABLED,
+
+        /**
+         * Used when autofill is enabled, so it tries to autofill the activity.
+         */
+        ASSERT_ENABLED_AND_AUTOFILL
+    }
+
+    /**
+     * Launches and finishes {@link SimpleSaveActivity}, returning how long it took.
+     */
+    private long launchSimpleSaveActivity(PostLaunchAction action) throws Exception {
+        Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
+        sReplier.assertNoUnhandledFillRequests();
+
+        if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+            sReplier.addResponse(new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(SimpleSaveActivity.ID_INPUT, "id")
+                            .setField(SimpleSaveActivity.ID_PASSWORD, "pass")
+                            .setPresentation(createPresentation("YO"))
+                            .build())
+                    .build());
+
+        }
+
+        final long before = SystemClock.elapsedRealtime();
+        final SimpleSaveActivity activity = startSimpleSaveActivity();
+        final MyAutofillCallback callback = activity.registerCallback();
+
+        try {
+            // Trigger autofill
+            activity.syncRunOnUiThread(() -> activity.mInput.requestFocus());
+
+            if (action == PostLaunchAction.ASSERT_DISABLING) {
+                callback.assertUiUnavailableEvent(activity.mInput);
+                sReplier.getNextFillRequest();
+
+                // Make sure other fields are not triggered.
+                activity.syncRunOnUiThread(() -> activity.mPassword.requestFocus());
+                callback.assertNotCalled();
+            } else if (action == PostLaunchAction.ASSERT_DISABLED) {
+                // Make sure forced requests are ignored as well.
+                activity.getAutofillManager().requestAutofill(activity.mInput);
+                callback.assertNotCalled();
+            } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+                callback.assertUiShownEvent(activity.mInput);
+                sReplier.getNextFillRequest();
+                final SimpleSaveActivity.FillExpectation autofillExpectation =
+                        activity.expectAutoFill("id", "pass");
+                mUiBot.selectDataset("YO");
+                autofillExpectation.assertAutoFilled();
+            }
+
+            // Asserts isEnabled() status.
+            assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+        } finally {
+            activity.finish();
+        }
+        return SystemClock.elapsedRealtime() - before;
+    }
+
+    /**
+     * Launches and finishes {@link PreSimpleSaveActivity}, returning how long it took.
+     */
+    private long launchPreSimpleSaveActivity(PostLaunchAction action) throws Exception {
+        Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
+        sReplier.assertNoUnhandledFillRequests();
+
+        if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+            sReplier.addResponse(new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(PreSimpleSaveActivity.ID_PRE_INPUT, "yo")
+                            .setPresentation(createPresentation("YO"))
+                            .build())
+                    .build());
+        }
+
+        final long before = SystemClock.elapsedRealtime();
+        final PreSimpleSaveActivity activity = startPreSimpleSaveActivity();
+        final MyAutofillCallback callback = activity.registerCallback();
+
+        try {
+            // Trigger autofill
+            activity.syncRunOnUiThread(() -> activity.mPreInput.requestFocus());
+
+            if (action == PostLaunchAction.ASSERT_DISABLING) {
+                callback.assertUiUnavailableEvent(activity.mPreInput);
+                sReplier.getNextFillRequest();
+            } else if (action == PostLaunchAction.ASSERT_DISABLED) {
+                activity.getAutofillManager().requestAutofill(activity.mPreInput);
+                callback.assertNotCalled();
+            } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+                callback.assertUiShownEvent(activity.mPreInput);
+                sReplier.getNextFillRequest();
+                final PreSimpleSaveActivity.FillExpectation autofillExpectation =
+                        activity.expectAutoFill("yo");
+                mUiBot.selectDataset("YO");
+                autofillExpectation.assertAutoFilled();
+            }
+
+            // Asserts isEnabled() status.
+            assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+        } finally {
+            activity.finish();
+        }
+        return SystemClock.elapsedRealtime() - before;
+    }
+
+    @Before
+    public void resetAutofillOptions() throws Exception {
+        // Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist.
+        Helper.resetApplicationAutofillOptions(sContext);
+    }
+
+    @Test
+    public void testDisableApp() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(
+                new CannedFillResponse.Builder().disableAutofill(Long.MAX_VALUE).build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Now try it using a different activity - should be disabled too.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDisableApp() is enough")
+    public void testDisableAppThenWaitToReenableIt() throws Exception {
+        // Set service.
+        enableService();
+
+        // Need to wait the equivalent of launching 2 activities, plus some extra legging room
+        final long duration = 2 * ACTIVITY_RESURRECTION.ms() + 500;
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder().disableAutofill(duration).build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Wait for the timeout, then try again, autofilling it this time.
+        sleep(passedTime, duration);
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Also try it on another activity.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDisableApp() is enough")
+    public void testDisableAppThenResetServiceToReenableIt() throws Exception {
+        enableService();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(Long.MAX_VALUE).build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Then "reset" service to re-enable autofill.
+        disableService();
+        enableService();
+
+        // Try again on activity that disabled it.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Try again on other activity.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Test
+    public void testDisableActivity() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(Long.MAX_VALUE)
+                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+                .build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Now try it using a different activity - should work.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDisableActivity() is enough")
+    public void testDisableActivityThenWaitToReenableIt() throws Exception {
+        // Set service.
+        enableService();
+
+        // Need to wait the equivalent of launching 2 activities, plus some extra legging room
+        final long duration = 2 * ACTIVITY_RESURRECTION.ms() + 500;
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(duration)
+                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+                .build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Make sure other app is working.
+        passedTime += launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Wait for the timeout, then try again, autofilling it this time.
+        sleep(passedTime, duration);
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDisableActivity() is enough")
+    public void testDisableActivityThenResetServiceToReenableIt() throws Exception {
+        enableService();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(Long.MAX_VALUE)
+                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+                .build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Make sure other app is working.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Then "reset" service to re-enable autofill.
+        disableService();
+        enableService();
+
+        // Try again on activity that disabled it.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    private void assertAutofillEnabled(AbstractAutoFillActivity activity, boolean expected)
+            throws Exception {
+        ACTIVITY_RESURRECTION.run(
+                "assertAutofillEnabled(" + activity.getComponentName().flattenToShortString() + ")",
+                () -> {
+                    return activity.getAutofillManager().isEnabled() == expected
+                            ? Boolean.TRUE : null;
+                });
+    }
+
+    private void sleep(long passedTime, long disableDuration) {
+        final long napTime = disableDuration - passedTime + 500;
+        if (napTime <= 0) {
+            // Throw an exception so ACTIVITY_RESURRECTION is increased
+            throw new RetryableException("took longer than expcted to launch activities: "
+                            + "passedTime=" + passedTime + "ms, disableDuration=" + disableDuration
+                            + ", ACTIVITY_RESURRECTION=" + ACTIVITY_RESURRECTION
+                            + ", CALLBACK_NOT_CALLED_TIMEOUT_MS=" + CALLBACK_NOT_CALLED_TIMEOUT_MS);
+        }
+        Log.v(TAG, "Sleeping for " + napTime + "ms (duration=" + disableDuration + "ms, passedTime="
+                + passedTime + ")");
+        SystemClock.sleep(napTime);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/FieldsClassificationTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/FieldsClassificationTest.java
new file mode 100644
index 0000000..b9f03f7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/FieldsClassificationTest.java
@@ -0,0 +1,860 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.servicebehavior;
+
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C2;
+import static android.autofillservice.cts.activities.GridActivity.ID_L2C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L2C2;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForContextCommitted;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForFieldsClassification;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.provider.Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
+import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_CREDIT_CARD;
+import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EDIT_DISTANCE;
+import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EXACT_MATCH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.commontests.AbstractGridActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper.FieldClassificationResult;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.UserData;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+@AppModeFull(reason = "Service-specific test")
+public class FieldsClassificationTest extends AbstractGridActivityTestCase {
+
+    @ClassRule
+    public static final SettingsStateChangerRule sFeatureEnabler =
+            new SettingsStateChangerRule(sContext, AUTOFILL_FEATURE_FIELD_CLASSIFICATION, "1");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
+            new SettingsStateChangerRule(sContext,
+                    AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "9");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMinValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "4");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxCategoryChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "42");
+
+    private AutofillManager mAfm;
+    private final Bundle mLast4Bundle = new Bundle();
+    private final Bundle mCreditCardBundle = new Bundle();
+
+    @Override
+    protected void postActivityLaunched() {
+        mAfm = mActivity.getAutofillManager();
+        mLast4Bundle.putInt("MATCH_SUFFIX", 4);
+
+        mCreditCardBundle.putInt("REQUIRED_ARG_MIN_CC_LENGTH", 13);
+        mCreditCardBundle.putInt("REQUIRED_ARG_MAX_CC_LENGTH", 19);
+        mCreditCardBundle.putInt("OPTIONAL_ARG_SUFFIX_LENGTH", 4);
+    }
+
+    @Test
+    public void testFeatureIsEnabled() throws Exception {
+        enableService();
+        assertThat(mAfm.isFieldClassificationEnabled()).isTrue();
+
+        disableService();
+        assertThat(mAfm.isFieldClassificationEnabled()).isFalse();
+    }
+
+    @Test
+    public void testGetAlgorithm() throws Exception {
+        enableService();
+
+        // Check algorithms
+        final List<String> names = mAfm.getAvailableFieldClassificationAlgorithms();
+        assertThat(names.size()).isAtLeast(1);
+        final String defaultAlgorithm = mAfm.getDefaultFieldClassificationAlgorithm();
+        assertThat(defaultAlgorithm).isNotEmpty();
+        assertThat(names).contains(defaultAlgorithm);
+
+        // Checks invalid service
+        disableService();
+        assertThat(mAfm.getAvailableFieldClassificationAlgorithms()).isEmpty();
+    }
+
+    @Test
+    public void testUserData() throws Exception {
+        assertThat(mAfm.getUserData()).isNull();
+        assertThat(mAfm.getUserDataId()).isNull();
+
+        enableService();
+        mAfm.setUserData(new UserData.Builder("user_data_id", "value", "remote_id")
+                .build());
+        assertThat(mAfm.getUserData()).isNotNull();
+        assertThat(mAfm.getUserDataId()).isEqualTo("user_data_id");
+        final UserData userData = mAfm.getUserData();
+        assertThat(userData.getId()).isEqualTo("user_data_id");
+        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
+        assertThat(userData.getFieldClassificationAlgorithms()).isNull();
+
+        disableService();
+        assertThat(mAfm.getUserData()).isNull();
+        assertThat(mAfm.getUserDataId()).isNull();
+    }
+
+    @Test
+    public void testRequiredAlgorithmsAvailable() throws Exception {
+        enableService();
+        final List<String> availableAlgorithms = mAfm.getAvailableFieldClassificationAlgorithms();
+        assertThat(availableAlgorithms).isNotNull();
+        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EDIT_DISTANCE)).isTrue();
+        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EXACT_MATCH)).isTrue();
+        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_CREDIT_CARD)).isTrue();
+    }
+
+    @Test
+    public void testUserDataConstraints() throws Exception {
+        // NOTE: values set by the SettingsStateChangerRule @Rules should have unique values to
+        // make sure the getters below are reading the right property.
+        assertThat(UserData.getMaxFieldClassificationIdsSize()).isEqualTo(10);
+        assertThat(UserData.getMaxUserDataSize()).isEqualTo(9);
+        assertThat(UserData.getMinValueLength()).isEqualTo(4);
+        assertThat(UserData.getMaxValueLength()).isEqualTo(50);
+        assertThat(UserData.getMaxCategoryCount()).isEqualTo(42);
+    }
+
+    @Test
+    public void testHit_oneUserData_oneDetectableField() throws Exception {
+        simpleHitTest(false, null);
+    }
+
+    @Test
+    public void testHit_invalidAlgorithmIsIgnored() throws Exception {
+        // For simplicity's sake, let's assume that name will never be valid..
+        String invalidName = " ALGORITHM, Y NO INVALID? ";
+
+        simpleHitTest(true, invalidName);
+    }
+
+    @Test
+    public void testHit_userDataAlgorithmIsReset() throws Exception {
+        simpleHitTest(true, null);
+    }
+
+    @Test
+    public void testMiss_exactMatchAlgorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "t 1234", "cat")
+                .setFieldClassificationAlgorithmForCategory("cat",
+                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "t 5678");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), null);
+    }
+
+    @Test
+    public void testHit_exactMatchLast4Algorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "1234", "cat")
+                .setFieldClassificationAlgorithmForCategory("cat",
+                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "T1234");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "cat", 1);
+    }
+
+    @Test
+    public void testHit_CreditCardAlgorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "1122334455667788", "card")
+                .setFieldClassificationAlgorithmForCategory("card",
+                        REQUIRED_ALGORITHM_CREDIT_CARD, mCreditCardBundle)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "7788");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "card", 1);
+    }
+
+    @Test
+    public void testHit_useDefaultAlgorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "1234", "cat")
+                .setFieldClassificationAlgorithm(REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .setFieldClassificationAlgorithmForCategory("dog",
+                        REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "T1234");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "cat", 1);
+    }
+
+    private void simpleHitTest(boolean setAlgorithm, String algorithm) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final UserData.Builder userData = new UserData.Builder("id", "FULLY", "myId");
+        if (setAlgorithm) {
+            userData.setFieldClassificationAlgorithm(algorithm, null);
+        }
+        mAfm.setUserData(userData.build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "myId", 1);
+    }
+
+    @Test
+    public void testHit_sameValueForMultipleCategories() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "FULLY", "cat1")
+                .add("FULLY", "cat2")
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId.get(),
+                                new String[] { "cat1", "cat2"},
+                                new float[] {1, 1})
+                });
+    }
+
+    @Test
+    public void testHit_manyUserData_oneDetectableField_bestMatchIsFirst() throws Exception {
+        manyUserData_oneDetectableField(true);
+    }
+
+    @Test
+    public void testHit_manyUserData_oneDetectableField_bestMatchIsSecond() throws Exception {
+        manyUserData_oneDetectableField(false);
+    }
+
+    private void manyUserData_oneDetectableField(boolean firstMatch) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "Iam1ST", "1stId")
+                .add("Iam2ND", "2ndId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, firstMatch ? "IAM111" : "IAM222");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        // Best match is 0.66 (4 of 6), worst is 0.5 (3 of 6)
+        if (firstMatch) {
+            assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
+                    new FieldClassificationResult(fieldId.get(), new String[] { "1stId", "2ndId" },
+                            new float[] { 0.66F, 0.5F })});
+        } else {
+            assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
+                    new FieldClassificationResult(fieldId.get(), new String[] { "2ndId", "1stId" },
+                            new float[] { 0.66F, 0.5F }) });
+        }
+    }
+
+    @Test
+    public void testHit_oneUserData_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
+                .setVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
+                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
+                })
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully"); // 100%
+        mActivity.setText(1, 2, "fooly"); // 60%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1.get(), "myId", 1.0F),
+                        new FieldClassificationResult(fieldId2.get(), "myId", 0.6F),
+                });
+    }
+
+    @Test
+    public void testHit_manyUserData_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId")
+                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
+                .add("EMPTY", "otherId")
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId4 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
+                .setVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
+                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
+                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
+                    fieldId4.set(findAutofillIdByResourceId(context, ID_L2C2));
+                })
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully"); // u1: 100% u2:  20%
+        mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
+        mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
+        mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId2.get(),
+                                new String[] { "otherId", "myId" }, new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId3.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 0.6F, 0.2F }),
+                        new FieldClassificationResult(fieldId4.get(),
+                                new String[] { "otherId", "myId"}, new float[] { 0.80F, 0.2F })});
+    }
+
+    @Test
+    public void testHit_manyUserData_manyDetectableFields_differentClassificationAlgo()
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "1234", "myId")
+                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
+                .add("EMPTY", "otherId")
+                .setFieldClassificationAlgorithmForCategory("myId",
+                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .setFieldClassificationAlgorithmForCategory("otherId",
+                        REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
+                .setVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
+                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
+                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
+                })
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "E1234"); // u1: 100% u2:  20%
+        mActivity.setText(1, 2, "empty"); // u1:   0% u2: 100%
+        mActivity.setText(2, 1, "fULLy"); // u1:   0% u2:  20%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId2.get(),
+                                new String[] { "otherId" }, new float[] { 1.0F }),
+                        new FieldClassificationResult(fieldId3.get(),
+                                new String[] { "otherId" }, new float[] { 0.2F })});
+    }
+
+    @Test
+    public void testHit_manyUserDataPerField_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "zzzzz", "myId") // should not have matched any
+                .add("FULL1", "myId") // match 80%, should not have been reported
+                .add("FULLY", "myId") // match 100%
+                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
+                .add("EMPTY", "otherId")
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId4 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
+                .setVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
+                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
+                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
+                    fieldId4.set(findAutofillIdByResourceId(context, ID_L2C2));
+                })
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully"); // u1: 100% u2:  20%
+        mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
+        mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
+        mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId2.get(),
+                                new String[] { "otherId", "myId" }, new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId3.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 0.6F, 0.2F }),
+                        new FieldClassificationResult(fieldId4.get(),
+                                new String[] { "otherId", "myId"}, new float[] { 0.80F, 0.2F })});
+    }
+
+    @Test
+    public void testMiss() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "ABCDEF", "myId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "xyz");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForContextCommitted(events.get(0));
+    }
+
+    @Test
+    public void testNoUserInput() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForContextCommitted(events.get(0));
+    }
+
+    @Test
+    public void testHit_usePackageUserData() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "TEST1", "cat")
+                .setFieldClassificationAlgorithm(null, null)
+                .build());
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId1
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .setUserData(new UserData.Builder("id2", "TEST2", "cat")
+                        .setFieldClassificationAlgorithm(null, null)
+                        .build())
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "test1");
+
+        // Finish context
+        mAfm.commit();
+
+        final Event packageUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
+        assertFillEventForFieldsClassification(packageUserDataEvent, fieldId1.get(), "cat", 0.8F);
+
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setVisitor((contexts, builder) -> fieldId2
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .setFieldClassificationIds(ID_L1C1)
+                .build());
+
+        // Need to switch focus first
+        mActivity.focusCell(1, 2);
+
+        // Trigger second autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final Event defaultUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
+        assertFillEventForFieldsClassification(defaultUserDataEvent, fieldId2.get(), "cat", 1.0F);
+    }
+
+    @Test
+    public void testHit_mergeUserData_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
+                .setVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
+                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
+                })
+                .setUserData(new UserData.Builder("id2", "FOOLY", "otherId")
+                        .add("EMPTY", "myId")
+                        .build())
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully"); // u1:  20%, u2: 60%
+        mActivity.setText(1, 2, "empty"); // u1: 100%, u2: 20%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1.get(),
+                                new String[] { "otherId", "myId" }, new float[] { 0.6F, 0.2F }),
+                        new FieldClassificationResult(fieldId2.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
+                });
+    }
+
+    /*
+     * TODO(b/73648631): other scenarios:
+     *
+     * - Multipartition (for example, one response with FieldsDetection, others with datasets,
+     *   saveinfo, and/or ignoredIds)
+     * - make sure detectable fields don't trigger a new partition
+     * v test partial hit (for example, 'fool' instead of 'full'
+     * v multiple fields
+     * v multiple value
+     * - combinations of above items
+     */
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java
new file mode 100644
index 0000000..45354e0
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.servicebehavior;
+
+import static android.autofillservice.cts.activities.PreSimpleSaveActivity.ID_PRE_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.activities.PreSimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.content.ComponentName;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+
+import org.junit.Test;
+
+@AppModeFull(reason = "Service-specific test")
+public class MultiScreenDifferentActivitiesTest
+        extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+    @Test
+    public void testActivityNotDelayedIsNotMerged() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger autofill on 1st activity, without using FLAG_DELAY_SAVE
+        final PreSimpleSaveActivity activity1 = startPreSimpleSaveActivity();
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_PRE_INPUT)
+                .build());
+
+        activity1.syncRunOnUiThread(() -> activity1.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger autofill on 2nd activity
+        final SimpleSaveActivity activity2 = startSimpleSaveActivity();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT)
+                .build());
+        activity2.syncRunOnUiThread(() -> activity2.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        activity2.syncRunOnUiThread(() -> {
+            activity2.mInput.setText("ID");
+            activity2.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Make sure only second request is available
+        assertThat(saveRequest.contexts).hasSize(1);
+
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
+    }
+
+    @Test
+    public void testDelayedActivityIsMerged() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger autofill on 1st activity, usingFLAG_DELAY_SAVE
+        final PreSimpleSaveActivity activity1 = startPreSimpleSaveActivity();
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_PRE_INPUT)
+                .build());
+
+        activity1.syncRunOnUiThread(() -> activity1.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Fill field but don't finish session yet
+        activity1.syncRunOnUiThread(() -> {
+            activity1.mPreInput.setText("PRE");
+        });
+
+        // Trigger autofill on 2nd activity
+        final SimpleSaveActivity activity2 = startSimpleSaveActivity();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT)
+                .build());
+        activity2.syncRunOnUiThread(() -> activity2.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        activity2.syncRunOnUiThread(() -> {
+            activity2.mInput.setText("ID");
+            activity2.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Make sure both requests are available
+        assertThat(saveRequest.contexts).hasSize(2);
+
+        // Assert 1st request
+        final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure();
+        assertWithMessage("no structure for 1st activity").that(structure1).isNotNull();
+        assertTextAndValue(findNodeByResourceId(structure1, ID_PRE_INPUT), "PRE");
+        assertThat(findNodeByResourceId(structure1, ID_INPUT)).isNull();
+        final ComponentName component1 = structure1.getActivityComponent();
+        assertThat(component1).isEqualTo(activity1.getComponentName());
+
+        // Assert 2nd request
+        final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure();
+        assertWithMessage("no structure for 2nd activity").that(structure2).isNotNull();
+        assertThat(findNodeByResourceId(structure2, ID_PRE_INPUT)).isNull();
+        assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "ID");
+        final ComponentName component2 = structure2.getActivityComponent();
+        assertThat(component2).isEqualTo(activity2.getComponentName());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenLoginTest.java
new file mode 100644
index 0000000..325e22d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenLoginTest.java
@@ -0,0 +1,429 @@
+/*
+ * 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.autofillservice.cts.servicebehavior;
+
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.newCustomDescriptionWithUsernameAndPassword;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.PasswordOnlyActivity;
+import android.autofillservice.cts.activities.UsernameOnlyActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.AutofillTestWatcher;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+
+import java.util.regex.Pattern;
+
+/**
+ * Test case for the senario where a login screen is split in multiple activities.
+ */
+@AppModeFull(reason = "Service-specific test")
+public class MultiScreenLoginTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<UsernameOnlyActivity> {
+
+    private static final String TAG = "MultiScreenLoginTest";
+    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
+
+    private UsernameOnlyActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<UsernameOnlyActivity> getActivityRule() {
+        return new AutofillActivityTestRule<UsernameOnlyActivity>(UsernameOnlyActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    /**
+     * Tests the "traditional" scenario where the service must save each field (username and
+     * password) separately.
+     */
+    @Test
+    public void testSaveEachFieldSeparately() throws Exception {
+        // Set service
+        enableService();
+
+        // First handle username...
+
+        // Set expectations.
+        final Bundle clientState1 = new Bundle();
+        clientState1.putString("first", "one");
+        clientState1.putString("last", "one");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+                .setExtras(clientState1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME);
+
+        // ..and assert results
+        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest1.structure, ID_USERNAME), "dude");
+        assertThat(saveRequest1.data.getString("first")).isEqualTo("one");
+        assertThat(saveRequest1.data.getString("last")).isEqualTo("one");
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity activity2 = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+        // Set expectations.
+        final Bundle clientState2 = new Bundle();
+        clientState2.putString("second", "two");
+        clientState2.putString("last", "two");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD)
+                .setExtras(clientState2)
+                .build());
+
+        // Trigger autofill
+        activity2.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        activity2.setPassword("sweet");
+        activity2.login();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // ..and assert results
+        final SaveRequest saveRequest2 = sReplier.getNextSaveRequest();
+        assertThat(saveRequest2.data.getString("first")).isNull();
+        assertThat(saveRequest2.data.getString("second")).isEqualTo("two");
+        assertThat(saveRequest2.data.getString("last")).isEqualTo("two");
+        assertTextAndValue(findNodeByResourceId(saveRequest2.structure, ID_PASSWORD), "sweet");
+    }
+
+    /**
+     * Tests the new scenario introudced on Q where the service can set a multi-screen session,
+     * with the service setting the client state just in the first request (so its passed to both
+     * the second fill request and the save request.
+     */
+    @Test
+    public void testSaveBothFieldsAtOnceNoClientStateOnSecondRequest() throws Exception {
+        // Set service
+        enableService();
+
+        // First handle username...
+
+        // Set expectations.
+        final Bundle clientState1 = new Bundle();
+        clientState1.putString("first", "one");
+        clientState1.putString("last", "one");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .setExtras(clientState1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
+        assertThat(component1).isEqualTo(mActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger what would be save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.assertSaveNotShowing();
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        ID_PASSWORD)
+                .build());
+
+        // Trigger autofill
+        passwordActivity.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
+        // Client state should come from 1st request
+        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
+        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
+
+        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
+        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        passwordActivity.setPassword("sweet");
+        passwordActivity.login();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
+
+        // ..and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        // Client state should come from 1st request
+        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
+        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
+
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        // Username is set in the 1st context
+        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
+        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(previousStructure, ID_USERNAME), "dude");
+        final ComponentName componentPrevious = previousStructure.getActivityComponent();
+        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
+
+        // Password is set in the 2nd context
+        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
+        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(currentStructure, ID_PASSWORD), "sweet");
+        final ComponentName componentCurrent = currentStructure.getActivityComponent();
+        assertThat(componentCurrent).isEqualTo(passwordActivity.getComponentName());
+    }
+
+    /**
+     * Tests the new scenario introudced on Q where the service can set a multi-screen session,
+     * with the service setting the client state just on both requests (so the 1st client state is
+     * passed to the 2nd request, and the 2nd client state is passed to the save request).
+     */
+    @Test
+    public void testSaveBothFieldsAtOnceWithClientStateOnBothRequests() throws Exception {
+        // Set service
+        enableService();
+
+        // First handle username...
+
+        // Set expectations.
+        final Bundle clientState1 = new Bundle();
+        clientState1.putString("first", "one");
+        clientState1.putString("last", "one");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .setExtras(clientState1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
+        assertThat(component1).isEqualTo(mActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger what would be save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.assertSaveNotShowing();
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+        // Set expectations.
+        final Bundle clientState2 = new Bundle();
+        clientState2.putString("second", "two");
+        clientState2.putString("last", "two");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        ID_PASSWORD)
+                .setExtras(clientState2)
+                .build());
+
+        // Trigger autofill
+        passwordActivity.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
+        // Client state on 2nd request should come from previous (1st) request
+        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
+        assertThat(fillRequest2.data.getString("second")).isNull();
+        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
+
+        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
+        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        passwordActivity.setPassword("sweet");
+        passwordActivity.login();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
+
+        // ..and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        // Client state on save request should come from last (2nd) request
+        assertThat(saveRequest.data.getString("first")).isNull();
+        assertThat(saveRequest.data.getString("second")).isEqualTo("two");
+        assertThat(saveRequest.data.getString("last")).isEqualTo("two");
+
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        // Username is set in the 1st context
+        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
+        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(previousStructure, ID_USERNAME), "dude");
+        final ComponentName componentPrevious = previousStructure.getActivityComponent();
+        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
+
+        // Password is set in the 2nd context
+        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
+        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(currentStructure, ID_PASSWORD), "sweet");
+        final ComponentName componentCurrent = currentStructure.getActivityComponent();
+        assertThat(componentCurrent).isEqualTo(passwordActivity.getComponentName());
+    }
+
+    @Test
+    public void testSaveBothFieldsCustomDescription_differentIds() throws Exception {
+        saveBothFieldsCustomDescription(false);
+    }
+
+    @Test
+    public void testSaveBothFieldsCustomDescription_sameIds() throws Exception {
+        saveBothFieldsCustomDescription(true);
+    }
+
+    private void saveBothFieldsCustomDescription(boolean sameAutofillId) throws Exception {
+        // Set service
+        enableService();
+
+        // Set ids
+        final AutofillId appUsernameId = mActivity.getUsernameAutofillId();
+        final AutofillId appPasswordId = sameAutofillId ? appUsernameId
+                : mActivity.getAutofillManager().getNextAutofillId();
+        mActivity.setPasswordAutofillId(appPasswordId);
+        Log.d(TAG, "App: usernameId=" + appUsernameId + ", passwordId=" + appPasswordId);
+
+        // First handle username...
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
+        assertThat(component1).isEqualTo(mActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger what would be save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.assertSaveNotShowing();
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+        // Must get AutofillIds from FillRequest, as they contain the proper session ids
+        final AutofillId svcUsernameId = findAutofillIdByResourceId(fillRequest1.contexts.get(0),
+                ID_USERNAME);
+        Log.d(TAG, "Service: usernameId=" + svcUsernameId);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setVisitor((contexts, builder) -> {
+                    final AutofillId svcPasswordId =
+                            findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD);
+                    Log.d(TAG, "Service: passwordId=" + svcPasswordId);
+                    final CharSequenceTransformation usernameTrans =
+                            new CharSequenceTransformation.Builder(svcUsernameId, MATCH_ALL, "$1")
+                            .build();
+                    final CharSequenceTransformation passwordTrans =
+                            new CharSequenceTransformation.Builder(svcPasswordId, MATCH_ALL, "$1")
+                            .build();
+                    builder.setSaveInfo(new SaveInfo.Builder(
+                            SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                            new AutofillId[] {svcPasswordId})
+                            .setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                                    .addChild(R.id.username, usernameTrans)
+                                    .addChild(R.id.password, passwordTrans)
+                                    .build())
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill
+        passwordActivity.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
+
+        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
+        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        passwordActivity.setPassword("sweet");
+        passwordActivity.login();
+
+        // ...and assert UI
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME,
+                SAVE_DATA_TYPE_PASSWORD);
+
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME, "dude");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD, "sweet");
+    }
+
+    // TODO(b/113281366): add test cases for more scenarios such as:
+    // - make sure that activity not marked with keepAlive is not sent in the 2nd request
+    // - somehow verify that the first activity's session is gone
+    // - WebView
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/SettingsIntentTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/SettingsIntentTest.java
new file mode 100644
index 0000000..d44293a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/SettingsIntentTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.autofillservice.cts.servicebehavior;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.autofillservice.cts.activities.TrampolineForResultActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.BadAutofillService;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillServiceCompatMode;
+import android.autofillservice.cts.testcore.NoOpAutofillService;
+import android.content.Intent;
+import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.support.test.uiautomator.UiObject2;
+
+import com.android.compatibility.common.util.FeatureUtil;
+
+import org.junit.After;
+import org.junit.Test;
+
+@AppModeFull(reason = "Service-specific test")
+public class SettingsIntentTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<TrampolineForResultActivity> {
+
+    private static final int MY_REQUEST_CODE = 42;
+
+
+    protected TrampolineForResultActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<TrampolineForResultActivity> getActivityRule() {
+        return new AutofillActivityTestRule<TrampolineForResultActivity>(
+                TrampolineForResultActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @After
+    public void killSettings() {
+        // Make sure there's no Settings activity left, as it could fail future tests.
+        if (FeatureUtil.isAutomotive()) {
+            runShellCommand("am force-stop com.android.car.settings");
+        } else {
+            runShellCommand("am force-stop com.android.settings");
+        }
+    }
+
+    @Test
+    public void testMultipleServicesShown() throws Exception {
+        disableService();
+
+        // Launches Settings.
+        mActivity.startForResult(newSettingsIntent(), MY_REQUEST_CODE);
+
+        // Asserts services are shown.
+        mUiBot.assertShownByText(InstrumentedAutoFillService.sServiceLabel);
+        mUiBot.assertShownByText(InstrumentedAutoFillServiceCompatMode.sServiceLabel);
+        mUiBot.scrollToTextObject(NoOpAutofillService.SERVICE_LABEL);
+        mUiBot.assertShownByText(NoOpAutofillService.SERVICE_LABEL);
+        mUiBot.assertNotShowingForSure(BadAutofillService.SERVICE_LABEL);
+
+        // Finishes and asserts result.
+        mUiBot.pressBack();
+        mActivity.assertResult(Activity.RESULT_CANCELED);
+    }
+
+    @Test
+    public void testWarningShown_userRejectsByTappingBack() throws Exception {
+        disableService();
+
+        // Launches Settings.
+        mActivity.startForResult(newSettingsIntent(), MY_REQUEST_CODE);
+
+        // Asserts services are shown.
+        final UiObject2 object = mUiBot
+                .assertShownByText(InstrumentedAutoFillService.sServiceLabel);
+        object.click();
+
+        // TODO(b/79615759): should assert that "autofill_confirmation_message" is shown, but that
+        // string belongs to Settings - we need to move it to frameworks/base first (and/or use
+        // a resource id, also on framework).
+        // So, for now, just asserts the service name is showing again (in the popup), and the other
+        // services are not showing (because the popup hides then).
+
+        final UiObject2 msgObj = mUiBot.assertShownById("android:id/message");
+        final String msg = msgObj.getText();
+        assertWithMessage("Wrong warning message").that(msg)
+                .contains(InstrumentedAutoFillService.sServiceLabel);
+
+        // NOTE: assertion below is fine because it looks for the full text, not a substring
+        mUiBot.assertNotShowingForSure(InstrumentedAutoFillService.sServiceLabel);
+        mUiBot.assertNotShowingForSure(InstrumentedAutoFillServiceCompatMode.sServiceLabel);
+        mUiBot.assertNotShowingForSure(NoOpAutofillService.SERVICE_LABEL);
+        mUiBot.assertNotShowingForSure(BadAutofillService.SERVICE_LABEL);
+
+        // Finishes and asserts result.
+        mUiBot.pressBack();
+        mActivity.assertResult(Activity.RESULT_CANCELED);
+    }
+
+    // TODO(b/79615759): add testWarningShown_userRejectsByTappingCancel() and
+    // testWarningShown_userAccepts() - these tests would require adding the strings and resource
+    // ids to frameworks/base
+
+    private Intent newSettingsIntent() {
+        return new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .setData(Uri.parse("package:" + Helper.MY_PACKAGE));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/ValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/ValidatorTest.java
new file mode 100644
index 0000000..c17819a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/ValidatorTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.servicebehavior;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.InternalValidator;
+import android.service.autofill.LuhnChecksumValidator;
+import android.service.autofill.ValueFinder;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+
+/**
+ * Simple integration test to verify that the UI is only shown if the validator passes.
+ */
+@AppModeFull(reason = "Service-specific test")
+public class ValidatorTest extends AbstractLoginActivityTestCase {
+
+    @Test
+    public void testShowUiWhenValidatorPass() throws Exception {
+        integrationTest(true);
+    }
+
+    @Test
+    public void testDontShowUiWhenValidatorFails() throws Exception {
+        integrationTest(false);
+    }
+
+    private void integrationTest(boolean willSaveBeShown) throws Exception {
+        enableService();
+
+        final String username = willSaveBeShown ? "7992739871-3" : "4815162342-108";
+
+        // Set response
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME, ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final AutofillId usernameId =
+                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
+                    final LuhnChecksumValidator validator = new LuhnChecksumValidator(usernameId);
+                    // Validation check to make sure the validator is properly configured
+                    assertValidator(validator, usernameId, username, willSaveBeShown);
+                    builder.setValidator(validator);
+                })
+                .build());
+
+        // Trigger auto-fill
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText(username));
+        mActivity.onPassword((v) -> v.setText("pass"));
+        mActivity.tapLogin();
+
+        if (willSaveBeShown) {
+            mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+            sReplier.getNextSaveRequest();
+        } else {
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        }
+    }
+
+    private void assertValidator(InternalValidator validator, AutofillId id, String text,
+            boolean valid) {
+        final ValueFinder valueFinder = mock(ValueFinder.class);
+        doReturn(text).when(valueFinder).findByAutofillId(id);
+        assertThat(validator.isValid(valueFinder)).isEqualTo(valid);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java
new file mode 100644
index 0000000..94a88c4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java
@@ -0,0 +1,325 @@
+/*
+ * 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.autofillservice.cts.servicebehavior;
+
+import static android.autofillservice.cts.activities.AbstractWebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.activities.AbstractWebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.newCustomDescriptionWithUsernameAndPassword;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByHtmlName;
+import static android.autofillservice.cts.testcore.Helper.getAutofillId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.MyWebView;
+import android.autofillservice.cts.activities.WebViewMultiScreenLoginActivity;
+import android.autofillservice.cts.commontests.AbstractWebViewTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.content.ComponentName;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Pattern;
+
+public class WebViewMultiScreenLoginActivityTest
+        extends AbstractWebViewTestCase<WebViewMultiScreenLoginActivity> {
+
+    private static final String TAG = "WebViewMultiScreenLoginTest";
+
+    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
+
+    private WebViewMultiScreenLoginActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<WebViewMultiScreenLoginActivity> getActivityRule() {
+        return new AutofillActivityTestRule<WebViewMultiScreenLoginActivity>(
+                WebViewMultiScreenLoginActivity.class) {
+
+            // TODO(b/111838239): latest WebView implementation calls AutofillManager.isEnabled() to
+            // disable autofill for optimization when it returns false, and unfortunately the value
+            // returned by that method does not change when the service is enabled / disabled, so we
+            // need to start enable the service before launching the activity.
+            // Once that's fixed, remove this overridden method.
+            @Override
+            protected void beforeActivityLaunched() {
+                super.beforeActivityLaunched();
+                Log.i(TAG, "Setting service before launching the activity");
+                enableService();
+            }
+
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testSave_eachFieldSeparately() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+        // Validation check to make sure autofill is enabled in the application context
+        Helper.assertAutofillEnabled(myWebView.getContext(), true);
+
+        /*
+         * First screen: username
+         */
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, HTML_NAME_USERNAME)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    final AutofillId usernameId = getAutofillId(nodeResolver, HTML_NAME_USERNAME);
+                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
+                            .Builder(usernameId, MATCH_ALL, "$1").build();
+                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                            .addChild(R.id.username, usernameTrans)
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        mUiBot.waitForIdle();
+
+        // check received request and no suggestion shown
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts).hasSize(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+        } else {
+            mActivity.getUsernameInput().setText("dude");
+        }
+        mActivity.getNextButton().click();
+        mUiBot.waitForIdle();
+
+        // Assert save UI shown.
+        final UiObject2 saveUi1 = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME);
+        mUiBot.assertChildText(saveUi1, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi1, ID_USERNAME, "dude");
+
+        // Save then assert save request and saveui disappear.
+        mUiBot.saveForAutofill(saveUi1, true);
+        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
+        assertThat(saveRequest1.contexts).hasSize(1);
+        assertTextAndValue(findNodeByHtmlName(saveRequest1.structure, HTML_NAME_USERNAME), "dude");
+        mUiBot.assertSaveNotShowing();
+
+        /*
+         * Second screen: password
+         */
+        mActivity.waitForPasswordScreen(mUiBot);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, HTML_NAME_PASSWORD)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
+                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
+                            .Builder(passwordId, MATCH_ALL, "$1").build();
+                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                            .addChild(R.id.password, passwordTrans)
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.getPasswordInput().click();
+        mUiBot.waitForIdle();
+
+        // check received request and no suggestion shown
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts).hasSize(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
+        } else {
+            mActivity.getPasswordInput().setText("sweet");
+        }
+        mActivity.getLoginButton().click();
+        mUiBot.waitForIdle();
+
+        // Assert save UI shown.
+        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertChildText(saveUi2, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi2, ID_PASSWORD, "sweet");
+
+        // Save then assert save request and saveui disappear.
+        mUiBot.saveForAutofill(saveUi2, true);
+        final SaveRequest saveRequest2 = sReplier.getNextSaveRequest();
+        assertThat(saveRequest2.contexts).hasSize(1);
+        assertTextAndValue(findNodeByHtmlName(saveRequest2.structure, HTML_NAME_PASSWORD), "sweet");
+        mUiBot.assertSaveNotShowing();
+    }
+
+    @Test
+    public void testSave_bothFieldsAtOnce() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+        // Validation check to make sure autofill is enabled in the application context
+        Helper.assertAutofillEnabled(myWebView.getContext(), true);
+
+        /*
+         * First screen: username
+         */
+        final AtomicReference<AutofillId> usernameId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setIgnoreFields(HTML_NAME_USERNAME)
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    usernameId.set(getAutofillId(nodeResolver, HTML_NAME_USERNAME));
+
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        mUiBot.waitForIdle();
+
+        // check received request and no suggestion shown
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts).hasSize(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Change username to trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+        } else {
+            mActivity.getUsernameInput().setText("dude");
+        }
+        mActivity.getNextButton().click();
+        mUiBot.waitForIdle();
+
+        // Assert save UI was not shown.
+        mUiBot.assertSaveNotShowing();
+
+        /*
+         * Second screen: password
+         */
+        mActivity.waitForPasswordScreen(mUiBot);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_PASSWORD)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
+                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
+                            .Builder(usernameId.get(), MATCH_ALL, "$1").build();
+                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
+                            .Builder(passwordId, MATCH_ALL, "$1").build();
+                    Log.d(TAG, "setting CustomDescription: u=" + usernameId + ", p=" + passwordId);
+                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                            .addChild(R.id.username, usernameTrans)
+                            .addChild(R.id.password, passwordTrans)
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.getPasswordInput().click();
+        mUiBot.waitForIdle();
+
+        // check received request and no suggestion shown
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts).hasSize(2);
+        mUiBot.assertNoDatasetsEver();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
+        } else {
+            mActivity.getPasswordInput().setText("sweet");
+        }
+        mActivity.getLoginButton().click();
+        mUiBot.waitForIdle();
+
+        // Assert save UI shown.
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME,
+                SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD, "sweet");
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME, "dude");
+
+        // Save then assert save request and saveui disappear.
+        mUiBot.saveForAutofill(saveUi, true);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        mUiBot.assertSaveNotShowing();
+
+        // Username is set in the 1st context
+        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
+        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
+        assertTextAndValue(findNodeByHtmlName(previousStructure, HTML_NAME_USERNAME), "dude");
+        final ComponentName componentPrevious = previousStructure.getActivityComponent();
+        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
+
+        // Password is set in the 2nd context
+        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
+        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
+        assertTextAndValue(findNodeByHtmlName(currentStructure, HTML_NAME_PASSWORD), "sweet");
+        final ComponentName componentCurrent = currentStructure.getActivityComponent();
+        assertThat(componentCurrent).isEqualTo(mActivity.getComponentName());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AntiTrimmerTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AntiTrimmerTextWatcher.java
new file mode 100644
index 0000000..00e9a9e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AntiTrimmerTextWatcher.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.widget.EditText;
+
+import java.util.regex.Pattern;
+
+/**
+ * A {@link TextWatcher} that appends pound signs ({@code #} at the beginning and end of the text.
+ */
+public final class AntiTrimmerTextWatcher implements TextWatcher {
+
+    /**
+     * Regex used to revert a String that was "anti-trimmed".
+     */
+    public static final Pattern TRIMMER_PATTERN = Pattern.compile("#(.*)#");
+
+    private final EditText mView;
+
+    public AntiTrimmerTextWatcher(EditText view) {
+        mView = view;
+        mView.addTextChangedListener(this);
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        mView.removeTextChangedListener(this);
+        mView.setText("#" + s + "#");
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java
new file mode 100644
index 0000000..0e3a6f6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java
@@ -0,0 +1,208 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.CONNECTION_TIMEOUT;
+import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.content.ComponentName;
+import android.service.autofill.augmented.FillRequest;
+import android.util.Log;
+import android.util.Pair;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper for common funcionalities.
+ */
+public final class AugmentedHelper {
+
+    private static final String TAG = AugmentedHelper.class.getSimpleName();
+
+    @NonNull
+    public static String getActivityName(@Nullable FillRequest request) {
+        if (request == null) return "N/A (null request)";
+
+        final ComponentName componentName = request.getActivityComponent();
+        if (componentName == null) return "N/A (no component name)";
+
+        return componentName.flattenToShortString();
+    }
+
+    /**
+     * Sets the augmented capture service.
+     */
+    public static void setAugmentedService(@NonNull String service) {
+        Log.d(TAG, "Setting service to " + service);
+        runShellCommand("cmd autofill set temporary-augmented-service 0 %s %d", service,
+                MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS);
+    }
+
+    /**
+     * Resets the content capture service.
+     */
+    public static void resetAugmentedService() {
+        Log.d(TAG, "Resetting back to default service");
+        runShellCommand("cmd autofill set temporary-augmented-service 0");
+    }
+
+    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
+            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
+            @NonNull AutofillValue expectedFocusedValue) {
+        assertBasicRequestInfo(request, activity, expectedFocusedId,
+                expectedFocusedValue.getTextValue().toString());
+    }
+
+    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
+            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
+            @NonNull String expectedFocusedValue) {
+        assertBasicRequestInfo(request, activity, expectedFocusedId, expectedFocusedValue, true);
+    }
+
+    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
+            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
+            @NonNull AutofillValue expectedFocusedValue, boolean hasInlineRequest) {
+        assertBasicRequestInfo(request, activity, expectedFocusedId,
+                expectedFocusedValue.getTextValue().toString(), hasInlineRequest);
+    }
+
+    private static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
+            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
+            @NonNull String expectedFocusedValue, boolean hasInlineRequest) {
+        Objects.requireNonNull(activity);
+        Objects.requireNonNull(expectedFocusedId);
+        assertWithMessage("no AugmentedFillRequest").that(request).isNotNull();
+        assertWithMessage("no FillRequest on %s", request).that(request.request).isNotNull();
+        assertWithMessage("no FillController on %s", request).that(request.controller).isNotNull();
+        assertWithMessage("no FillCallback on %s", request).that(request.callback).isNotNull();
+        assertWithMessage("no CancellationSignal on %s", request).that(request.cancellationSignal)
+                .isNotNull();
+        // NOTE: task id can change, we might need to set it in the activity's onCreate()
+        assertWithMessage("wrong task id on %s", request).that(request.request.getTaskId())
+                .isEqualTo(activity.getTaskId());
+
+        final ComponentName actualComponentName = request.request.getActivityComponent();
+        assertWithMessage("no activity name on %s", request).that(actualComponentName).isNotNull();
+        assertWithMessage("wrong activity name on %s", request).that(actualComponentName)
+                .isEqualTo(activity.getComponentName());
+        final AutofillId actualFocusedId = request.request.getFocusedId();
+        assertWithMessage("no focused id on %s", request).that(actualFocusedId).isNotNull();
+        assertWithMessage("wrong focused id on %s", request).that(actualFocusedId)
+                .isEqualTo(expectedFocusedId);
+        final AutofillValue actualFocusedValue = request.request.getFocusedValue();
+        assertWithMessage("no focused value on %s", request).that(actualFocusedValue).isNotNull();
+        assertAutofillValue(expectedFocusedValue, actualFocusedValue);
+        final InlineSuggestionsRequest inlineRequest =
+                request.request.getInlineSuggestionsRequest();
+        if (hasInlineRequest) {
+            assertWithMessage("no inline request on %s", request).that(inlineRequest).isNotNull();
+        } else {
+            assertWithMessage("exist inline request on %s", request).that(inlineRequest).isNull();
+        }
+    }
+
+    public static void assertAutofillValue(@NonNull AutofillValue expectedValue,
+            @NonNull AutofillValue actualValue) {
+        // It only supports text values for now...
+        assertWithMessage("expected value is not text: %s", expectedValue)
+                .that(expectedValue.isText()).isTrue();
+        assertAutofillValue(expectedValue.getTextValue().toString(), actualValue);
+    }
+
+    public static void assertAutofillValue(@NonNull String expectedValue,
+            @NonNull AutofillValue actualValue) {
+        assertWithMessage("actual value is not text: %s", actualValue)
+                .that(actualValue.isText()).isTrue();
+
+        assertWithMessage("wrong autofill value").that(actualValue.getTextValue().toString())
+                .isEqualTo(expectedValue);
+    }
+
+    @NonNull
+    public static String toString(@Nullable List<Pair<AutofillId, AutofillValue>> values) {
+        if (values == null) return "null";
+        final StringBuilder string = new StringBuilder("[");
+        final int size = values.size();
+        for (int i = 0; i < size; i++) {
+            final Pair<AutofillId, AutofillValue> value = values.get(i);
+            string.append(i).append(':').append(value.first).append('=')
+                   .append(Helper.toString(value.second));
+            if (i < size - 1) {
+                string.append(", ");
+            }
+
+        }
+        return string.append(']').toString();
+    }
+
+    @NonNull
+    public static String toString(@Nullable FillRequest request) {
+        if (request == null) return "(null request)";
+
+        final StringBuilder string =
+                new StringBuilder("FillRequest[act=").append(getActivityName(request))
+                .append(", taskId=").append(request.getTaskId());
+
+        final AutofillId focusedId = request.getFocusedId();
+        if (focusedId != null) {
+            string.append(", focusedId=").append(focusedId);
+        }
+        final AutofillValue focusedValue = request.getFocusedValue();
+        if (focusedValue != null) {
+            string.append(", focusedValue=").append(focusedValue);
+        }
+
+        return string.append(']').toString();
+    }
+
+    // Used internally by UiBot to assert the UI
+    public static String getContentDescriptionForUi(@NonNull AutofillId focusedId) {
+        return "ui_for_" + focusedId;
+    }
+
+    private AugmentedHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+
+    /**
+     * Awaits for a latch to be counted down.
+     */
+    public static void await(@NonNull CountDownLatch latch, @NonNull String fmt,
+            @Nullable Object... args)
+            throws InterruptedException {
+        final boolean called = latch.await(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        if (!called) {
+            throw new IllegalStateException(String.format(fmt, args)
+                    + " in " + CONNECTION_TIMEOUT.ms() + "ms");
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedTimeouts.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedTimeouts.java
new file mode 100644
index 0000000..d853b58
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedTimeouts.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import com.android.compatibility.common.util.Timeout;
+
+/**
+ * Timeouts for common tasks.
+ */
+public final class AugmentedTimeouts {
+
+    private static final long ONE_TIMEOUT_TO_RULE_THEN_ALL_MS = 1_000;
+    private static final long ONE_NAPTIME_TO_RULE_THEN_ALL_MS = 3_000;
+
+    /**
+     * Timeout for expected augmented autofill requests.
+     */
+    public static final Timeout AUGMENTED_FILL_TIMEOUT = new Timeout("AUGMENTED_FILL_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout until framework binds / unbinds from service.
+     */
+    public static final Timeout AUGMENTED_CONNECTION_TIMEOUT = new Timeout(
+            "AUGMENTED_CONNECTION_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F,
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout used when the augmented autofill UI not expected to be shown - test will sleep for
+     * that amount of time as there is no callback that be received to assert it's not shown.
+     */
+    public static final long AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+
+    private AugmentedTimeouts() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedUiBot.java
new file mode 100644
index 0000000..825db8f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedUiBot.java
@@ -0,0 +1,92 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.AugmentedHelper.getContentDescriptionForUi;
+import static android.autofillservice.cts.testcore.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
+import static android.autofillservice.cts.testcore.AugmentedTimeouts.AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.support.test.uiautomator.UiObject2;
+import android.view.autofill.AutofillId;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.base.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Helper for UI-related needs.
+ */
+public final class AugmentedUiBot {
+
+    private final UiBot mUiBot;
+    private boolean mOkToCallAssertUiGone;
+
+    public AugmentedUiBot(@NonNull UiBot uiBot) {
+        mUiBot = uiBot;
+    }
+
+    /**
+     * Asserts the augmented autofill UI was never shown.
+     *
+     * <p>This method is slower than {@link #assertUiGone()} and should only be called in the
+     * cases where the dataset picker was not previous shown.
+     */
+    public void assertUiNeverShown() throws Exception {
+        mUiBot.assertNeverShownByRelativeId("augmented autofil UI", R.id.augmentedAutofillUi,
+                AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS);
+    }
+
+    /**
+     * Asserts the augmented autofill UI was shown.
+     *
+     * @param focusedId where it should have been shown
+     * @param expectedText the expected text in the UI
+     */
+    public UiObject2 assertUiShown(@NonNull AutofillId focusedId,
+            @NonNull String expectedText) throws Exception {
+        Objects.requireNonNull(focusedId);
+        Objects.requireNonNull(expectedText);
+
+        final UiObject2 ui = mUiBot.assertShownByRelativeId(R.id.augmentedAutofillUi);
+
+        assertWithMessage("Wrong text on UI").that(ui.getText()).isEqualTo(expectedText);
+
+        final String expectedContentDescription = getContentDescriptionForUi(focusedId);
+        assertWithMessage("Wrong content description on UI")
+                .that(ui.getContentDescription()).isEqualTo(expectedContentDescription);
+
+        mOkToCallAssertUiGone = true;
+
+        return ui;
+    }
+
+    /**
+     * Asserts the augmented autofill UI is gone AFTER it was previously shown.
+     *
+     * @throws IllegalStateException if this method is called without calling
+     * {@link #assertUiShown(AutofillId, String)} before.
+     */
+    public void assertUiGone() {
+        Preconditions.checkState(mOkToCallAssertUiGone, "must call assertUiShown() first");
+        mUiBot.assertGoneByRelativeId(R.id.augmentedAutofillUi, AUGMENTED_FILL_TIMEOUT);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillActivityTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillActivityTestRule.java
new file mode 100644
index 0000000..4e75871
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillActivityTestRule.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+
+import androidx.test.rule.ActivityTestRule;
+
+/**
+ * Custom {@link ActivityTestRule}.
+ */
+public class AutofillActivityTestRule<T extends AbstractAutoFillActivity>
+        extends ActivityTestRule<T> {
+
+    public AutofillActivityTestRule(Class<T> activityClass) {
+        super(activityClass);
+    }
+
+    public AutofillActivityTestRule(Class<T> activityClass, boolean launchActivity) {
+        super(activityClass, false, launchActivity);
+    }
+
+    @Override
+    protected void afterActivityFinished() {
+        // AutofillTestWatcher does not need to watch for this activity as the ActivityTestRule
+        // will take care of finishing it...
+        AutofillTestWatcher.unregisterActivity("AutofillActivityTestRule.afterActivityFinished()",
+                getActivity());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillLoggingTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillLoggingTestRule.java
new file mode 100644
index 0000000..04ce7e0
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillLoggingTestRule.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.SafeCleanerRule;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Custom JUnit4 rule that improves autofill-related logging by:
+ *
+ * <ol>
+ *   <li>Setting logging level to verbose before test start.
+ *   <li>Call {@code dumpsys autofill} in case of failure.
+ * </ol>
+ */
+public class AutofillLoggingTestRule implements TestRule, SafeCleanerRule.Dumper {
+
+    private static final String TAG = "AutofillLoggingTestRule";
+
+    private final String mTag;
+    private boolean mDumped;
+
+    public AutofillLoggingTestRule(String tag) {
+        mTag = tag;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                final String testName = description.getDisplayName();
+                final String levelBefore = runShellCommand("cmd autofill get log_level");
+                if (!levelBefore.equals("verbose")) {
+                    runShellCommand("cmd autofill set log_level verbose");
+                }
+                try {
+                    base.evaluate();
+                } catch (Throwable t) {
+                    dump(testName, t);
+                    throw t;
+                } finally {
+                    try {
+                        if (!levelBefore.equals("verbose")) {
+                            runShellCommand("cmd autofill set log_level %s", levelBefore);
+                        }
+                    } finally {
+                        Log.v(TAG, "@After " + testName);
+                    }
+                }
+            }
+        };
+    }
+
+    @Override
+    public void dump(@NonNull String testName, @NonNull Throwable t) {
+        if (mDumped) {
+            Log.e(mTag, "dump(" + testName + "): already dumped");
+            return;
+        }
+        if ((t instanceof AssumptionViolatedException)) {
+            // This exception is used to indicate a test should be skipped and is
+            // ignored by JUnit runners - we don't need to dump it...
+            Log.w(TAG, "ignoring exception: " + t);
+            return;
+        }
+        Log.e(mTag, "Dumping after exception on " + testName, t);
+        Helper.dumpAutofillService(mTag);
+        final String activityDump = runShellCommand("dumpsys activity top");
+        Log.e(mTag, "top activity dump: \n" + activityDump);
+        mDumped = true;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillTestWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillTestWatcher.java
new file mode 100644
index 0000000..8cd3953
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillTestWatcher.java
@@ -0,0 +1,166 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.TestNameUtils;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.util.Set;
+
+/**
+ * Custom {@link TestWatcher} that's the outer rule of all {@link AutoFillServiceTestCase} tests.
+ *
+ * <p>This class is not thread safe, but should be fine...
+ */
+public final class AutofillTestWatcher extends TestWatcher {
+
+    /**
+     * Cleans up all launched activities between the tests and retries.
+     */
+    public void cleanAllActivities() {
+        try {
+            finishActivities();
+            waitUntilAllDestroyed();
+        } finally {
+            resetStaticState();
+        }
+    }
+
+    private static final String TAG = "AutofillTestWatcher";
+
+    @GuardedBy("sUnfinishedBusiness")
+    private static final Set<AbstractAutoFillActivity> sUnfinishedBusiness = new ArraySet<>();
+
+    @GuardedBy("sAllActivities")
+    private static final Set<AbstractAutoFillActivity> sAllActivities = new ArraySet<>();
+
+    @Override
+    protected void starting(Description description) {
+        resetStaticState();
+        final String testName = description.getDisplayName();
+        Log.i(TAG, "Starting " + testName);
+        TestNameUtils.setCurrentTestName(testName);
+    }
+
+    @Override
+    protected void finished(Description description) {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        final String testName = description.getDisplayName();
+        cleanAllActivities();
+        Log.i(TAG, "Finished " + testName);
+        TestNameUtils.setCurrentTestName(null);
+    }
+
+    private void resetStaticState() {
+        synchronized (sUnfinishedBusiness) {
+            sUnfinishedBusiness.clear();
+        }
+        synchronized (sAllActivities) {
+            sAllActivities.clear();
+        }
+    }
+
+    /**
+     * Registers an activity so it's automatically finished (if necessary) after the test.
+     */
+    public static void registerActivity(@NonNull String where,
+            @NonNull AbstractAutoFillActivity activity) {
+        synchronized (sUnfinishedBusiness) {
+            if (sUnfinishedBusiness.contains(activity)) {
+                throw new IllegalStateException("Already registered " + activity);
+            }
+            Log.v(TAG, "registering activity on " + where + ": " + activity);
+            sUnfinishedBusiness.add(activity);
+            sAllActivities.add(activity);
+        }
+        synchronized (sAllActivities) {
+            sAllActivities.add(activity);
+
+        }
+    }
+
+    /**
+     * Unregisters an activity so it's not automatically finished after the test.
+     */
+    public static void unregisterActivity(@NonNull String where,
+            @NonNull AbstractAutoFillActivity activity) {
+        synchronized (sUnfinishedBusiness) {
+            final boolean unregistered = sUnfinishedBusiness.remove(activity);
+            if (unregistered) {
+                Log.d(TAG, "unregistered activity on " + where + ": " + activity);
+            } else {
+                Log.v(TAG, "ignoring already unregistered activity on " + where + ": " + activity);
+            }
+        }
+    }
+
+    /**
+     * Gets the instance of a previously registered activity.
+     */
+    @Nullable
+    public static <A extends AbstractAutoFillActivity> A getActivity(@NonNull Class<A> clazz) {
+        @SuppressWarnings("unchecked")
+        final A activity = (A) sAllActivities.stream().filter(a -> a.getClass().equals(clazz))
+                .findFirst()
+                .get();
+        return activity;
+    }
+
+    private void finishActivities() {
+        synchronized (sUnfinishedBusiness) {
+            if (sUnfinishedBusiness.isEmpty()) {
+                return;
+            }
+            Log.d(TAG, "Manually finishing " + sUnfinishedBusiness.size() + " activities");
+            for (AbstractAutoFillActivity activity : sUnfinishedBusiness) {
+                if (activity.isFinishing()) {
+                    Log.v(TAG, "Ignoring activity that isFinishing(): " + activity);
+                } else {
+                    Log.d(TAG, "Finishing activity: " + activity);
+                    activity.finishOnly();
+                }
+            }
+        }
+    }
+
+    private void waitUntilAllDestroyed() {
+        synchronized (sAllActivities) {
+            if (sAllActivities.isEmpty()) return;
+
+            Log.d(TAG, "Waiting until " + sAllActivities.size() + " activities are destroyed");
+            for (AbstractAutoFillActivity activity : sAllActivities) {
+                Log.d(TAG, "Waiting for " + activity);
+                try {
+                    activity.waintUntilDestroyed(Timeouts.ACTIVITY_RESURRECTION);
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "interrupted waiting for " + activity + " to be destroyed");
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/BadAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/BadAutofillService.java
new file mode 100644
index 0000000..6165083
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/BadAutofillService.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.util.Log;
+
+/**
+ * An {@link AutofillService} implementation that does fails if called upon.
+ */
+public class BadAutofillService extends AutofillService {
+
+    private static final String TAG = "BadAutofillService";
+
+    public static final String SERVICE_NAME = BadAutofillService.class.getPackage().getName()
+            + "/.testcore." + BadAutofillService.class.getSimpleName();
+    public static final String SERVICE_LABEL = "BadAutofillService";
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+        Log.e(TAG, "onFillRequest() should never be called");
+        throw new UnsupportedOperationException("onFillRequest() should never be called");
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        Log.e(TAG, "onSaveRequest() should never be called");
+        throw new UnsupportedOperationException("onSaveRequest() should never be called");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedAugmentedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedAugmentedFillResponse.java
new file mode 100644
index 0000000..8443c6d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedAugmentedFillResponse.java
@@ -0,0 +1,378 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.AugmentedHelper.getContentDescriptionForUi;
+
+import android.autofillservice.cts.R;
+import android.content.Context;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.service.autofill.InlinePresentation;
+import android.service.autofill.augmented.FillCallback;
+import android.service.autofill.augmented.FillController;
+import android.service.autofill.augmented.FillRequest;
+import android.service.autofill.augmented.FillResponse;
+import android.service.autofill.augmented.FillWindow;
+import android.service.autofill.augmented.PresentationParams;
+import android.service.autofill.augmented.PresentationParams.Area;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Helper class used to produce a {@link FillResponse}.
+ */
+public final class CannedAugmentedFillResponse {
+
+    private static final String TAG = CannedAugmentedFillResponse.class.getSimpleName();
+
+    public static final String CLIENT_STATE_KEY = "clientStateKey";
+    public static final String CLIENT_STATE_VALUE = "clientStateValue";
+
+    private final AugmentedResponseType mResponseType;
+    private final Map<AutofillId, Dataset> mDatasets;
+    private long mDelay;
+    private final Dataset mOnlyDataset;
+    private final @Nullable List<Dataset> mInlineSuggestions;
+
+    private CannedAugmentedFillResponse(@NonNull Builder builder) {
+        mResponseType = builder.mResponseType;
+        mDatasets = builder.mDatasets;
+        mDelay = builder.mDelay;
+        mOnlyDataset = builder.mOnlyDataset;
+        mInlineSuggestions = builder.mInlineSuggestions;
+    }
+
+    /**
+     * Constant used to pass a {@code null} response to the
+     * {@link FillCallback#onSuccess(FillResponse)} method.
+     */
+    public static final CannedAugmentedFillResponse NO_AUGMENTED_RESPONSE =
+            new Builder(AugmentedResponseType.NULL).build();
+
+    /**
+     * Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
+     */
+    public static final CannedAugmentedFillResponse DO_NOT_REPLY_AUGMENTED_RESPONSE =
+            new Builder(AugmentedResponseType.TIMEOUT).build();
+
+    public AugmentedResponseType getResponseType() {
+        return mResponseType;
+    }
+
+    public long getDelay() {
+        return mDelay;
+    }
+
+    /**
+     * Creates the "real" response.
+     */
+    public FillResponse asFillResponse(@NonNull Context context, @NonNull FillRequest request,
+            @NonNull FillController controller) {
+        final AutofillId focusedId = request.getFocusedId();
+
+        final Dataset dataset;
+        if (mOnlyDataset != null) {
+            dataset = mOnlyDataset;
+        } else {
+            dataset = mDatasets.get(focusedId);
+        }
+        if (dataset == null) {
+            Log.d(TAG, "no dataset for field " + focusedId);
+            return null;
+        }
+
+        Log.d(TAG, "asFillResponse: id=" + focusedId + ", dataset=" + dataset);
+
+        final PresentationParams presentationParams = request.getPresentationParams();
+        if (presentationParams == null) {
+            Log.w(TAG, "No PresentationParams");
+            return null;
+        }
+
+        final Area strip = presentationParams.getSuggestionArea();
+        if (strip == null) {
+            Log.w(TAG, "No suggestion strip");
+            return null;
+        }
+
+        if (mInlineSuggestions != null) {
+            return createResponseWithInlineSuggestion();
+        }
+
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        final TextView rootView = (TextView) inflater.inflate(R.layout.augmented_autofill_ui, null);
+
+        Log.d(TAG, "Setting autofill UI text to:" + dataset.mPresentation);
+        rootView.setText(dataset.mPresentation);
+
+        rootView.setContentDescription(getContentDescriptionForUi(focusedId));
+        final FillWindow fillWindow = new FillWindow();
+        rootView.setOnClickListener((v) -> {
+            Log.d(TAG, "Destroying window first");
+            fillWindow.destroy();
+            final List<Pair<AutofillId, AutofillValue>> values;
+            final AutofillValue onlyValue = dataset.getOnlyFieldValue();
+            if (onlyValue != null) {
+                Log.i(TAG, "Autofilling only value for " + focusedId + " as " + onlyValue);
+                values = new ArrayList<>(1);
+                values.add(new Pair<AutofillId, AutofillValue>(focusedId, onlyValue));
+            } else {
+                values = dataset.getValues();
+                Log.i(TAG, "Autofilling: " + AugmentedHelper.toString(values));
+            }
+            controller.autofill(values);
+        });
+
+        boolean ok = fillWindow.update(strip, rootView, 0);
+        if (!ok) {
+            Log.w(TAG, "FillWindow.update() failed for " + strip + " and " + rootView);
+            return null;
+        }
+
+        return new FillResponse.Builder().setFillWindow(fillWindow).build();
+    }
+
+    @Override
+    public String toString() {
+        return "CannedAugmentedFillResponse: [type=" + mResponseType
+                + ", onlyDataset=" + mOnlyDataset
+                + ", datasets=" + mDatasets
+                + "]";
+    }
+
+    public enum AugmentedResponseType {
+        NORMAL,
+        NULL,
+        TIMEOUT,
+    }
+
+    private Bundle newClientState() {
+        Bundle b = new Bundle();
+        b.putString(CLIENT_STATE_KEY, CLIENT_STATE_VALUE);
+        return b;
+    }
+
+    private FillResponse createResponseWithInlineSuggestion() {
+        List<android.service.autofill.Dataset> list = new ArrayList<>();
+        for (Dataset dataset : mInlineSuggestions) {
+            if (!dataset.getValues().isEmpty()) {
+                android.service.autofill.Dataset.Builder datasetBuilder =
+                        new android.service.autofill.Dataset.Builder();
+                for (Pair<AutofillId, AutofillValue> pair : dataset.getValues()) {
+                    final AutofillId id = pair.first;
+                    datasetBuilder.setFieldInlinePresentation(id, pair.second, null,
+                            dataset.mFieldPresentationById.get(id));
+                    datasetBuilder.setAuthentication(dataset.mAuthentication);
+                }
+                list.add(datasetBuilder.build());
+            }
+        }
+        return new FillResponse.Builder().setInlineSuggestions(list).setClientState(
+                newClientState()).build();
+    }
+
+    public static final class Builder {
+        private final Map<AutofillId, Dataset> mDatasets = new ArrayMap<>();
+        private final AugmentedResponseType mResponseType;
+        private long mDelay;
+        private Dataset mOnlyDataset;
+        private @Nullable List<Dataset> mInlineSuggestions;
+
+        public Builder(@NonNull AugmentedResponseType type) {
+            mResponseType = type;
+        }
+
+        public Builder() {
+            this(AugmentedResponseType.NORMAL);
+        }
+
+        /**
+         * Sets the {@link Dataset} that will be filled when the given {@code ids} is focused and
+         * the UI is tapped.
+         */
+        @NonNull
+        public Builder setDataset(@NonNull Dataset dataset, @NonNull AutofillId... ids) {
+            if (mOnlyDataset != null) {
+                throw new IllegalStateException("already called setOnlyDataset()");
+            }
+            for (AutofillId id : ids) {
+                mDatasets.put(id, dataset);
+            }
+            return this;
+        }
+
+        /**
+         * The {@link android.service.autofill.Dataset}s representing the inline suggestions data.
+         * Defaults to null if no inline suggestions are available from the service.
+         */
+        @NonNull
+        public Builder addInlineSuggestion(@NonNull Dataset dataset) {
+            if (mInlineSuggestions == null) {
+                mInlineSuggestions = new ArrayList<>();
+            }
+            mInlineSuggestions.add(dataset);
+            return this;
+        }
+
+        /**
+         * Sets the delay for onFillRequest().
+         */
+        public Builder setDelay(long delay) {
+            mDelay = delay;
+            return this;
+        }
+
+        /**
+         * Sets the only dataset that will be returned.
+         *
+         * <p>Used when the test case doesn't know the autofill id of the focused field.
+         * @param dataset
+         */
+        @NonNull
+        public Builder setOnlyDataset(@NonNull Dataset dataset) {
+            if (!mDatasets.isEmpty()) {
+                throw new IllegalStateException("already called setDataset()");
+            }
+            mOnlyDataset = dataset;
+            return this;
+        }
+
+        @NonNull
+        public CannedAugmentedFillResponse build() {
+            return new CannedAugmentedFillResponse(this);
+        }
+    } // CannedAugmentedFillResponse.Builder
+
+
+    /**
+     * Helper class used to define which fields will be autofilled when the user taps the Augmented
+     * Autofill UI.
+     */
+    public static class Dataset {
+        private final Map<AutofillId, AutofillValue> mFieldValuesById;
+        private final Map<AutofillId, InlinePresentation> mFieldPresentationById;
+        private final String mPresentation;
+        private final AutofillValue mOnlyFieldValue;
+        private final IntentSender mAuthentication;
+
+        private Dataset(@NonNull Builder builder) {
+            mFieldValuesById = builder.mFieldValuesById;
+            mPresentation = builder.mPresentation;
+            mOnlyFieldValue = builder.mOnlyFieldValue;
+            mFieldPresentationById = builder.mFieldPresentationById;
+            this.mAuthentication = builder.mAuthentication;
+        }
+
+        @NonNull
+        public List<Pair<AutofillId, AutofillValue>> getValues() {
+            return mFieldValuesById.entrySet().stream()
+                    .map((entry) -> (new Pair<>(entry.getKey(), entry.getValue())))
+                    .collect(Collectors.toList());
+        }
+
+        @Nullable
+        public AutofillValue getOnlyFieldValue() {
+            return mOnlyFieldValue;
+        }
+
+        @Override
+        public String toString() {
+            return "Dataset: [presentation=" + mPresentation
+                    + ", onlyField=" + mOnlyFieldValue
+                    + ", fields=" + mFieldValuesById
+                    + ", auth=" + mAuthentication
+                    + "]";
+        }
+
+        public static class Builder {
+            private final Map<AutofillId, AutofillValue> mFieldValuesById = new ArrayMap<>();
+            private final Map<AutofillId, InlinePresentation> mFieldPresentationById =
+                    new ArrayMap<>();
+
+            private final String mPresentation;
+            private AutofillValue mOnlyFieldValue;
+            private IntentSender mAuthentication;
+
+            public Builder(@NonNull String presentation) {
+                mPresentation = Objects.requireNonNull(presentation);
+            }
+
+            /**
+             * Sets the value that will be autofilled on the field with {@code id}.
+             */
+            public Builder setField(@NonNull AutofillId id, @NonNull String text) {
+                if (mOnlyFieldValue != null) {
+                    throw new IllegalStateException("already called setOnlyField()");
+                }
+                mFieldValuesById.put(id, AutofillValue.forText(text));
+                return this;
+            }
+
+            /**
+             * Sets the value that will be autofilled on the field with {@code id}.
+             */
+            public Builder setField(@NonNull AutofillId id, @NonNull String text,
+                    @NonNull InlinePresentation presentation) {
+                if (mOnlyFieldValue != null) {
+                    throw new IllegalStateException("already called setOnlyField()");
+                }
+                mFieldValuesById.put(id, AutofillValue.forText(text));
+                mFieldPresentationById.put(id, presentation);
+                return this;
+            }
+
+            /**
+             * Sets this dataset to return the given {@code text} for the focused field.
+             *
+             * <p>Used when the test case doesn't know the autofill id of the focused field.
+             */
+            public Builder setOnlyField(@NonNull String text) {
+                if (!mFieldValuesById.isEmpty()) {
+                    throw new IllegalStateException("already called setField()");
+                }
+                mOnlyFieldValue = AutofillValue.forText(text);
+                return this;
+            }
+
+            /**
+             * Sets the authentication intent for this dataset.
+             */
+            public Builder setAuthentication(IntentSender authentication) {
+                mAuthentication = authentication;
+                return this;
+            }
+
+            public Dataset build() {
+                return new Dataset(this);
+            }
+        } // Dataset.Builder
+    } // Dataset
+} // CannedAugmentedFillResponse
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
new file mode 100644
index 0000000..8ef8627
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
@@ -0,0 +1,888 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Helper.createInlinePresentation;
+import static android.autofillservice.cts.testcore.Helper.createPresentation;
+import static android.autofillservice.cts.testcore.Helper.getAutofillIds;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillResponse;
+import android.service.autofill.InlinePresentation;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.UserData;
+import android.util.Log;
+import android.util.Pair;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class used to produce a {@link FillResponse} based on expected fields that should be
+ * present in the {@link AssistStructure}.
+ *
+ * <p>Typical usage:
+ *
+ * <pre class="prettyprint">
+ * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
+ *               .addDataset(new CannedDataset.Builder("dataset_name")
+ *                   .setField("resource_id1", AutofillValue.forText("value1"))
+ *                   .setField("resource_id2", AutofillValue.forText("value2"))
+ *                   .build())
+ *               .build());
+ * </pre class="prettyprint">
+ */
+public final class CannedFillResponse {
+
+    private static final String TAG = CannedFillResponse.class.getSimpleName();
+
+    private final ResponseType mResponseType;
+    private final List<CannedDataset> mDatasets;
+    private final String mFailureMessage;
+    private final int mSaveType;
+    private final String[] mRequiredSavableIds;
+    private final String[] mOptionalSavableIds;
+    private final AutofillId[] mRequiredSavableAutofillIds;
+    private final CharSequence mSaveDescription;
+    private final Bundle mExtras;
+    private final RemoteViews mPresentation;
+    private final InlinePresentation mInlinePresentation;
+    private final RemoteViews mHeader;
+    private final RemoteViews mFooter;
+    private final IntentSender mAuthentication;
+    private final String[] mAuthenticationIds;
+    private final String[] mIgnoredIds;
+    private final int mNegativeActionStyle;
+    private final IntentSender mNegativeActionListener;
+    private final int mPositiveActionStyle;
+    private final int mSaveInfoFlags;
+    private final int mFillResponseFlags;
+    private final AutofillId mSaveTriggerId;
+    private final long mDisableDuration;
+    private final String[] mFieldClassificationIds;
+    private final boolean mFieldClassificationIdsOverflow;
+    private final SaveInfoDecorator mSaveInfoDecorator;
+    private final UserData mUserData;
+    private final DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
+    private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
+    private final int[] mCancelIds;
+
+    private CannedFillResponse(Builder builder) {
+        mResponseType = builder.mResponseType;
+        mDatasets = builder.mDatasets;
+        mFailureMessage = builder.mFailureMessage;
+        mRequiredSavableIds = builder.mRequiredSavableIds;
+        mRequiredSavableAutofillIds = builder.mRequiredSavableAutofillIds;
+        mOptionalSavableIds = builder.mOptionalSavableIds;
+        mSaveDescription = builder.mSaveDescription;
+        mSaveType = builder.mSaveType;
+        mExtras = builder.mExtras;
+        mPresentation = builder.mPresentation;
+        mInlinePresentation = builder.mInlinePresentation;
+        mHeader = builder.mHeader;
+        mFooter = builder.mFooter;
+        mAuthentication = builder.mAuthentication;
+        mAuthenticationIds = builder.mAuthenticationIds;
+        mIgnoredIds = builder.mIgnoredIds;
+        mNegativeActionStyle = builder.mNegativeActionStyle;
+        mNegativeActionListener = builder.mNegativeActionListener;
+        mPositiveActionStyle = builder.mPositiveActionStyle;
+        mSaveInfoFlags = builder.mSaveInfoFlags;
+        mFillResponseFlags = builder.mFillResponseFlags;
+        mSaveTriggerId = builder.mSaveTriggerId;
+        mDisableDuration = builder.mDisableDuration;
+        mFieldClassificationIds = builder.mFieldClassificationIds;
+        mFieldClassificationIdsOverflow = builder.mFieldClassificationIdsOverflow;
+        mSaveInfoDecorator = builder.mSaveInfoDecorator;
+        mUserData = builder.mUserData;
+        mVisitor = builder.mVisitor;
+        mSaveInfoVisitor = builder.mSaveInfoVisitor;
+        mCancelIds = builder.mCancelIds;
+    }
+
+    /**
+     * Constant used to pass a {@code null} response to the
+     * {@link FillCallback#onSuccess(FillResponse)} method.
+     */
+    public static final CannedFillResponse NO_RESPONSE =
+            new Builder(ResponseType.NULL).build();
+
+    /**
+     * Constant used to fail the test when an expected request was made.
+     */
+    public static final CannedFillResponse NO_MOAR_RESPONSES =
+            new Builder(ResponseType.NO_MORE).build();
+
+    /**
+     * Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
+     */
+    public static final CannedFillResponse DO_NOT_REPLY_RESPONSE =
+            new Builder(ResponseType.TIMEOUT).build();
+
+    /**
+     * Constant used to call {@link FillCallback#onFailure(CharSequence)} method.
+     */
+    public static final CannedFillResponse FAIL =
+            new Builder(ResponseType.FAILURE).build();
+
+    public String getFailureMessage() {
+        return mFailureMessage;
+    }
+
+    public ResponseType getResponseType() {
+        return mResponseType;
+    }
+
+    /**
+     * Creates a new response, replacing the dataset field ids by the real ids from the assist
+     * structure.
+     */
+    public FillResponse asFillResponse(@Nullable List<FillContext> contexts,
+            @NonNull Function<String, ViewNode> nodeResolver) {
+        final FillResponse.Builder builder = new FillResponse.Builder()
+                .setFlags(mFillResponseFlags);
+        if (mDatasets != null) {
+            for (CannedDataset cannedDataset : mDatasets) {
+                final Dataset dataset = cannedDataset.asDataset(nodeResolver);
+                assertWithMessage("Cannot create datase").that(dataset).isNotNull();
+                builder.addDataset(dataset);
+            }
+        }
+        final SaveInfo.Builder saveInfoBuilder;
+        if (mRequiredSavableIds != null || mOptionalSavableIds != null
+                || mRequiredSavableAutofillIds != null || mSaveInfoDecorator != null) {
+            if (mRequiredSavableAutofillIds != null) {
+                saveInfoBuilder = new SaveInfo.Builder(mSaveType, mRequiredSavableAutofillIds);
+            } else {
+                saveInfoBuilder = mRequiredSavableIds == null || mRequiredSavableIds.length == 0
+                        ? new SaveInfo.Builder(mSaveType)
+                            : new SaveInfo.Builder(mSaveType,
+                                    getAutofillIds(nodeResolver, mRequiredSavableIds));
+            }
+
+            saveInfoBuilder.setFlags(mSaveInfoFlags);
+
+            if (mOptionalSavableIds != null) {
+                saveInfoBuilder.setOptionalIds(getAutofillIds(nodeResolver, mOptionalSavableIds));
+            }
+            if (mSaveDescription != null) {
+                saveInfoBuilder.setDescription(mSaveDescription);
+            }
+            if (mNegativeActionListener != null) {
+                saveInfoBuilder.setNegativeAction(mNegativeActionStyle, mNegativeActionListener);
+            }
+
+            saveInfoBuilder.setPositiveAction(mPositiveActionStyle);
+
+            if (mSaveTriggerId != null) {
+                saveInfoBuilder.setTriggerId(mSaveTriggerId);
+            }
+        } else if (mSaveInfoFlags != 0) {
+            saveInfoBuilder = new SaveInfo.Builder(mSaveType).setFlags(mSaveInfoFlags);
+        } else {
+            saveInfoBuilder = null;
+        }
+        if (saveInfoBuilder != null) {
+            // TODO: merge decorator and visitor
+            if (mSaveInfoDecorator != null) {
+                mSaveInfoDecorator.decorate(saveInfoBuilder, nodeResolver);
+            }
+            if (mSaveInfoVisitor != null) {
+                Log.d(TAG, "Visiting saveInfo " + saveInfoBuilder);
+                mSaveInfoVisitor.visit(contexts, saveInfoBuilder);
+            }
+            final SaveInfo saveInfo = saveInfoBuilder.build();
+            Log.d(TAG, "saveInfo:" + saveInfo);
+            builder.setSaveInfo(saveInfo);
+        }
+        if (mIgnoredIds != null) {
+            builder.setIgnoredIds(getAutofillIds(nodeResolver, mIgnoredIds));
+        }
+        if (mAuthenticationIds != null) {
+            builder.setAuthentication(getAutofillIds(nodeResolver, mAuthenticationIds),
+                    mAuthentication, mPresentation, mInlinePresentation);
+        }
+        if (mDisableDuration > 0) {
+            builder.disableAutofill(mDisableDuration);
+        }
+        if (mFieldClassificationIdsOverflow) {
+            final int length = UserData.getMaxFieldClassificationIdsSize() + 1;
+            final AutofillId[] fieldIds = new AutofillId[length];
+            for (int i = 0; i < length; i++) {
+                fieldIds[i] = new AutofillId(i);
+            }
+            builder.setFieldClassificationIds(fieldIds);
+        } else if (mFieldClassificationIds != null) {
+            builder.setFieldClassificationIds(
+                    getAutofillIds(nodeResolver, mFieldClassificationIds));
+        }
+        if (mExtras != null) {
+            builder.setClientState(mExtras);
+        }
+        if (mHeader != null) {
+            builder.setHeader(mHeader);
+        }
+        if (mFooter != null) {
+            builder.setFooter(mFooter);
+        }
+        if (mUserData != null) {
+            builder.setUserData(mUserData);
+        }
+        if (mVisitor != null) {
+            Log.d(TAG, "Visiting " + builder);
+            mVisitor.visit(contexts, builder);
+        }
+        builder.setPresentationCancelIds(mCancelIds);
+
+        final FillResponse response = builder.build();
+        Log.v(TAG, "Response: " + response);
+        return response;
+    }
+
+    @Override
+    public String toString() {
+        return "CannedFillResponse: [type=" + mResponseType
+                + ",datasets=" + mDatasets
+                + ", requiredSavableIds=" + Arrays.toString(mRequiredSavableIds)
+                + ", optionalSavableIds=" + Arrays.toString(mOptionalSavableIds)
+                + ", requiredSavableAutofillIds=" + Arrays.toString(mRequiredSavableAutofillIds)
+                + ", saveInfoFlags=" + mSaveInfoFlags
+                + ", fillResponseFlags=" + mFillResponseFlags
+                + ", failureMessage=" + mFailureMessage
+                + ", saveDescription=" + mSaveDescription
+                + ", hasPresentation=" + (mPresentation != null)
+                + ", hasInlinePresentation=" + (mInlinePresentation != null)
+                + ", hasHeader=" + (mHeader != null)
+                + ", hasFooter=" + (mFooter != null)
+                + ", hasAuthentication=" + (mAuthentication != null)
+                + ", authenticationIds=" + Arrays.toString(mAuthenticationIds)
+                + ", ignoredIds=" + Arrays.toString(mIgnoredIds)
+                + ", saveTriggerId=" + mSaveTriggerId
+                + ", disableDuration=" + mDisableDuration
+                + ", fieldClassificationIds=" + Arrays.toString(mFieldClassificationIds)
+                + ", fieldClassificationIdsOverflow=" + mFieldClassificationIdsOverflow
+                + ", saveInfoDecorator=" + mSaveInfoDecorator
+                + ", userData=" + mUserData
+                + ", visitor=" + mVisitor
+                + ", saveInfoVisitor=" + mSaveInfoVisitor
+                + "]";
+    }
+
+    public enum ResponseType {
+        NORMAL,
+        NULL,
+        NO_MORE,
+        TIMEOUT,
+        FAILURE,
+        DELAY
+    }
+
+    public static final class Builder {
+        private final List<CannedDataset> mDatasets = new ArrayList<>();
+        private final ResponseType mResponseType;
+        private String mFailureMessage;
+        private String[] mRequiredSavableIds;
+        private String[] mOptionalSavableIds;
+        private AutofillId[] mRequiredSavableAutofillIds;
+        private CharSequence mSaveDescription;
+        public int mSaveType = -1;
+        private Bundle mExtras;
+        private RemoteViews mPresentation;
+        private InlinePresentation mInlinePresentation;
+        private RemoteViews mFooter;
+        private RemoteViews mHeader;
+        private IntentSender mAuthentication;
+        private String[] mAuthenticationIds;
+        private String[] mIgnoredIds;
+        private int mNegativeActionStyle;
+        private IntentSender mNegativeActionListener;
+        private int mPositiveActionStyle;
+        private int mSaveInfoFlags;
+        private int mFillResponseFlags;
+        private AutofillId mSaveTriggerId;
+        private long mDisableDuration;
+        private String[] mFieldClassificationIds;
+        private boolean mFieldClassificationIdsOverflow;
+        private SaveInfoDecorator mSaveInfoDecorator;
+        private UserData mUserData;
+        private DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
+        private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
+        private int[] mCancelIds;
+
+        public Builder(ResponseType type) {
+            mResponseType = type;
+        }
+
+        public Builder() {
+            this(ResponseType.NORMAL);
+        }
+
+        public Builder addDataset(CannedDataset dataset) {
+            assertWithMessage("already set failure").that(mFailureMessage).isNull();
+            mDatasets.add(dataset);
+            return this;
+        }
+
+        /**
+         * Sets the required savable ids based on their {@code resourceId}.
+         */
+        public Builder setRequiredSavableIds(int type, String... ids) {
+            mSaveType = type;
+            mRequiredSavableIds = ids;
+            return this;
+        }
+
+        public Builder setSaveInfoFlags(int flags) {
+            mSaveInfoFlags = flags;
+            return this;
+        }
+
+        public Builder setFillResponseFlags(int flags) {
+            mFillResponseFlags = flags;
+            return this;
+        }
+
+        /**
+         * Sets the optional savable ids based on they {@code resourceId}.
+         */
+        public Builder setOptionalSavableIds(String... ids) {
+            mOptionalSavableIds = ids;
+            return this;
+        }
+
+        /**
+         * Sets the description passed to the {@link SaveInfo}.
+         */
+        public Builder setSaveDescription(CharSequence description) {
+            mSaveDescription = description;
+            return this;
+        }
+
+        /**
+         * Sets the extra passed to {@link
+         * android.service.autofill.FillResponse.Builder#setClientState(Bundle)}.
+         */
+        public Builder setExtras(Bundle data) {
+            mExtras = data;
+            return this;
+        }
+
+        /**
+         * Sets the view to present the response in the UI.
+         */
+        public Builder setPresentation(RemoteViews presentation) {
+            mPresentation = presentation;
+            return this;
+        }
+
+        /**
+         * Sets the view to present the response in the UI.
+         */
+        public Builder setInlinePresentation(InlinePresentation inlinePresentation) {
+            mInlinePresentation = inlinePresentation;
+            return this;
+        }
+
+        /**
+         * Sets views to present the response in the UI by the type.
+         */
+        public Builder setPresentation(String message, boolean inlineMode) {
+            mPresentation = createPresentation(message);
+            if (inlineMode) {
+                mInlinePresentation = createInlinePresentation(message);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the authentication intent.
+         */
+        public Builder setAuthentication(IntentSender authentication, String... ids) {
+            mAuthenticationIds = ids;
+            mAuthentication = authentication;
+            return this;
+        }
+
+        /**
+         * Sets the ignored fields based on resource ids.
+         */
+        public Builder setIgnoreFields(String...ids) {
+            mIgnoredIds = ids;
+            return this;
+        }
+
+        /**
+         * Sets the negative action spec.
+         */
+        public Builder setNegativeAction(int style, IntentSender listener) {
+            mNegativeActionStyle = style;
+            mNegativeActionListener = listener;
+            return this;
+        }
+
+        /**
+         * Sets the positive action spec.
+         */
+        public Builder setPositiveAction(int style) {
+            mPositiveActionStyle = style;
+            return this;
+        }
+
+        public CannedFillResponse build() {
+            return new CannedFillResponse(this);
+        }
+
+        /**
+         * Sets the response to call {@link FillCallback#onFailure(CharSequence)}.
+         */
+        public Builder returnFailure(String message) {
+            assertWithMessage("already added datasets").that(mDatasets).isEmpty();
+            mFailureMessage = message;
+            return this;
+        }
+
+        /**
+         * Sets the view that explicitly triggers save.
+         */
+        public Builder setSaveTriggerId(AutofillId id) {
+            assertWithMessage("already set").that(mSaveTriggerId).isNull();
+            mSaveTriggerId = id;
+            return this;
+        }
+
+        public Builder disableAutofill(long duration) {
+            assertWithMessage("already set").that(mDisableDuration).isEqualTo(0L);
+            mDisableDuration = duration;
+            return this;
+        }
+
+        /**
+         * Sets the ids used for field classification.
+         */
+        public Builder setFieldClassificationIds(String... ids) {
+            assertWithMessage("already set").that(mFieldClassificationIds).isNull();
+            mFieldClassificationIds = ids;
+            return this;
+        }
+
+        /**
+         * Forces the service to throw an exception when setting the fields classification ids.
+         */
+        public Builder setFieldClassificationIdsOverflow() {
+            mFieldClassificationIdsOverflow = true;
+            return this;
+        }
+
+        public Builder setHeader(RemoteViews header) {
+            assertWithMessage("already set").that(mHeader).isNull();
+            mHeader = header;
+            return this;
+        }
+
+        public Builder setFooter(RemoteViews footer) {
+            assertWithMessage("already set").that(mFooter).isNull();
+            mFooter = footer;
+            return this;
+        }
+
+        public Builder setSaveInfoDecorator(SaveInfoDecorator decorator) {
+            assertWithMessage("already set").that(mSaveInfoDecorator).isNull();
+            mSaveInfoDecorator = decorator;
+            return this;
+        }
+
+        /**
+         * Sets the package-specific UserData.
+         *
+         * <p>Overrides the default UserData for field classification.
+         */
+        public Builder setUserData(UserData userData) {
+            assertWithMessage("already set").that(mUserData).isNull();
+            mUserData = userData;
+            return this;
+        }
+
+        /**
+         * Sets a generic visitor for the "real" request and response.
+         *
+         * <p>Typically used in cases where the test need to infer data from the request to build
+         * the response.
+         */
+        public Builder setVisitor(
+                @NonNull DoubleVisitor<List<FillContext>, FillResponse.Builder> visitor) {
+            mVisitor = visitor;
+            return this;
+        }
+
+        /**
+         * Sets a generic visitor for the "real" request and save info.
+         *
+         * <p>Typically used in cases where the test need to infer data from the request to build
+         * the response.
+         */
+        public Builder setSaveInfoVisitor(
+                @NonNull DoubleVisitor<List<FillContext>, SaveInfo.Builder> visitor) {
+            mSaveInfoVisitor = visitor;
+            return this;
+        }
+
+        /**
+         * Sets targets that cancel current session
+         */
+        public Builder setPresentationCancelIds(int[] ids) {
+            mCancelIds = ids;
+            return this;
+        }
+    }
+
+    /**
+     * Helper class used to produce a {@link Dataset} based on expected fields that should be
+     * present in the {@link AssistStructure}.
+     *
+     * <p>Typical usage:
+     *
+     * <pre class="prettyprint">
+     * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
+     *               .addDataset(new CannedDataset.Builder("dataset_name")
+     *                   .setField("resource_id1", AutofillValue.forText("value1"))
+     *                   .setField("resource_id2", AutofillValue.forText("value2"))
+     *                   .build())
+     *               .build());
+     * </pre class="prettyprint">
+     */
+    public static class CannedDataset {
+        private final Map<String, AutofillValue> mFieldValues;
+        private final Map<String, RemoteViews> mFieldPresentations;
+        private final Map<String, InlinePresentation> mFieldInlinePresentations;
+        private final Map<String, Pair<Boolean, Pattern>> mFieldFilters;
+        private final RemoteViews mPresentation;
+        private final InlinePresentation mInlinePresentation;
+        private final IntentSender mAuthentication;
+        private final String mId;
+
+        private CannedDataset(Builder builder) {
+            mFieldValues = builder.mFieldValues;
+            mFieldPresentations = builder.mFieldPresentations;
+            mFieldInlinePresentations = builder.mFieldInlinePresentations;
+            mFieldFilters = builder.mFieldFilters;
+            mPresentation = builder.mPresentation;
+            mInlinePresentation = builder.mInlinePresentation;
+            mAuthentication = builder.mAuthentication;
+            mId = builder.mId;
+        }
+
+        /**
+         * Creates a new dataset, replacing the field ids by the real ids from the assist structure.
+         */
+        public Dataset asDataset(Function<String, ViewNode> nodeResolver) {
+            final Dataset.Builder builder = mPresentation != null
+                    ? mInlinePresentation == null
+                    ? new Dataset.Builder(mPresentation)
+                    : new Dataset.Builder(mPresentation).setInlinePresentation(mInlinePresentation)
+                    : mInlinePresentation == null
+                            ? new Dataset.Builder()
+                            : new Dataset.Builder(mInlinePresentation);
+
+            if (mFieldValues != null) {
+                for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
+                    final String id = entry.getKey();
+                    final ViewNode node = nodeResolver.apply(id);
+                    if (node == null) {
+                        throw new AssertionError("No node with resource id " + id);
+                    }
+                    final AutofillId autofillId = node.getAutofillId();
+                    final AutofillValue value = entry.getValue();
+                    final RemoteViews presentation = mFieldPresentations.get(id);
+                    final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(id);
+                    final Pair<Boolean, Pattern> filter = mFieldFilters.get(id);
+                    if (presentation != null) {
+                        if (filter == null) {
+                            if (inlinePresentation != null) {
+                                builder.setValue(autofillId, value, presentation,
+                                        inlinePresentation);
+                            } else {
+                                builder.setValue(autofillId, value, presentation);
+                            }
+                        } else {
+                            if (inlinePresentation != null) {
+                                builder.setValue(autofillId, value, filter.second, presentation,
+                                        inlinePresentation);
+                            } else {
+                                builder.setValue(autofillId, value, filter.second, presentation);
+                            }
+                        }
+                    } else {
+                        if (inlinePresentation != null) {
+                            builder.setFieldInlinePresentation(autofillId, value,
+                                    filter != null ? filter.second : null, inlinePresentation);
+                        } else {
+                            if (filter == null) {
+                                builder.setValue(autofillId, value);
+                            } else {
+                                builder.setValue(autofillId, value, filter.second);
+                            }
+                        }
+                    }
+                }
+            }
+            builder.setId(mId).setAuthentication(mAuthentication);
+            return builder.build();
+        }
+
+        @Override
+        public String toString() {
+            return "CannedDataset " + mId + " : [hasPresentation=" + (mPresentation != null)
+                    + ", hasInlinePresentation=" + (mInlinePresentation != null)
+                    + ", fieldPresentations=" + (mFieldPresentations)
+                    + ", fieldInlinePresentations=" + (mFieldInlinePresentations)
+                    + ", hasAuthentication=" + (mAuthentication != null)
+                    + ", fieldValues=" + mFieldValues
+                    + ", fieldFilters=" + mFieldFilters + "]";
+        }
+
+        public static class Builder {
+            private final Map<String, AutofillValue> mFieldValues = new HashMap<>();
+            private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
+            private final Map<String, InlinePresentation> mFieldInlinePresentations =
+                    new HashMap<>();
+            private final Map<String, Pair<Boolean, Pattern>> mFieldFilters = new HashMap<>();
+
+            private RemoteViews mPresentation;
+            private InlinePresentation mInlinePresentation;
+            private IntentSender mAuthentication;
+            private String mId;
+
+            public Builder() {
+
+            }
+
+            public Builder(RemoteViews presentation) {
+                mPresentation = presentation;
+            }
+
+            /**
+             * Sets the canned value of a text field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text) {
+                return setField(id, AutofillValue.forText(text));
+            }
+
+            /**
+             * Sets the canned value of a text field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text, Pattern filter) {
+                return setField(id, AutofillValue.forText(text), true, filter);
+            }
+
+            public Builder setUnfilterableField(String id, String text) {
+                return setField(id, AutofillValue.forText(text), false, null);
+            }
+
+            /**
+             * Sets the canned value of a list field based on its its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, int index) {
+                return setField(id, AutofillValue.forList(index));
+            }
+
+            /**
+             * Sets the canned value of a toggle field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, boolean toggled) {
+                return setField(id, AutofillValue.forToggle(toggled));
+            }
+
+            /**
+             * Sets the canned value of a date field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, long date) {
+                return setField(id, AutofillValue.forDate(date));
+            }
+
+            /**
+             * Sets the canned value of a date field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, AutofillValue value) {
+                mFieldValues.put(id, value);
+                return this;
+            }
+
+            /**
+             * Sets the canned value of a date field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, AutofillValue value, boolean filterable,
+                    Pattern filter) {
+                setField(id, value);
+                mFieldFilters.put(id, new Pair<>(filterable, filter));
+                return this;
+            }
+
+            /**
+             * Sets the canned value of a field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text, RemoteViews presentation) {
+                setField(id, text);
+                mFieldPresentations.put(id, presentation);
+                return this;
+            }
+
+            /**
+             * Sets the canned value of a field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text, RemoteViews presentation,
+                    Pattern filter) {
+                setField(id, text, presentation);
+                mFieldFilters.put(id, new Pair<>(true, filter));
+                return this;
+            }
+
+            /**
+             * Sets the canned value of a field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text, RemoteViews presentation,
+                    InlinePresentation inlinePresentation) {
+                setField(id, text);
+                mFieldPresentations.put(id, presentation);
+                mFieldInlinePresentations.put(id, inlinePresentation);
+                return this;
+            }
+
+            /**
+             * Sets the canned value of a field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text, RemoteViews presentation,
+                    InlinePresentation inlinePresentation, Pattern filter) {
+                setField(id, text, presentation, inlinePresentation);
+                mFieldFilters.put(id, new Pair<>(true, filter));
+                return this;
+            }
+
+            /**
+             * Sets the view to present the response in the UI.
+             */
+            public Builder setPresentation(RemoteViews presentation) {
+                mPresentation = presentation;
+                return this;
+            }
+
+            /**
+             * Sets the view to present the response in the UI.
+             */
+            public Builder setInlinePresentation(InlinePresentation inlinePresentation) {
+                mInlinePresentation = inlinePresentation;
+                return this;
+            }
+
+            public Builder setPresentation(String message, boolean inlineMode) {
+                mPresentation = createPresentation(message);
+                if (inlineMode) {
+                    mInlinePresentation = createInlinePresentation(message);
+                }
+                return this;
+            }
+
+            /**
+             * Sets the authentication intent.
+             */
+            public Builder setAuthentication(IntentSender authentication) {
+                mAuthentication = authentication;
+                return this;
+            }
+
+            /**
+             * Sets the name.
+             */
+            public Builder setId(String id) {
+                mId = id;
+                return this;
+            }
+
+            /**
+             * Builds the canned dataset.
+             */
+            public CannedDataset build() {
+                return new CannedDataset(this);
+            }
+        }
+    }
+
+    public interface SaveInfoDecorator {
+        void decorate(SaveInfo.Builder builder, Function<String, ViewNode> nodeResolver);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CtsAugmentedAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CtsAugmentedAutofillService.java
new file mode 100644
index 0000000..5810c11
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CtsAugmentedAutofillService.java
@@ -0,0 +1,442 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.AugmentedHelper.await;
+import static android.autofillservice.cts.testcore.AugmentedHelper.getActivityName;
+import static android.autofillservice.cts.testcore.AugmentedTimeouts.AUGMENTED_CONNECTION_TIMEOUT;
+import static android.autofillservice.cts.testcore.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.AugmentedResponseType.NULL;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.AugmentedResponseType.TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.FILL_EVENTS_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.augmented.AugmentedAutofillService;
+import android.service.autofill.augmented.FillCallback;
+import android.service.autofill.augmented.FillController;
+import android.service.autofill.augmented.FillRequest;
+import android.service.autofill.augmented.FillResponse;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.TestNameUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of {@link AugmentedAutofillService} used in the tests.
+ */
+public class CtsAugmentedAutofillService extends AugmentedAutofillService {
+
+    private static final String TAG = CtsAugmentedAutofillService.class.getSimpleName();
+
+    public static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
+    public static final String SERVICE_CLASS = CtsAugmentedAutofillService.class.getSimpleName();
+
+    public static final String SERVICE_NAME = SERVICE_PACKAGE + "/.testcore." + SERVICE_CLASS;
+
+    private static final AugmentedReplier sAugmentedReplier = new AugmentedReplier();
+
+    // We must handle all requests in a separate thread as the service's main thread is the also
+    // the UI thread of the test process and we don't want to hose it in case of failures here
+    private static final HandlerThread sMyThread = new HandlerThread("MyAugmentedServiceThread");
+    private final Handler mHandler;
+
+    private final CountDownLatch mConnectedLatch = new CountDownLatch(1);
+    private final CountDownLatch mDisconnectedLatch = new CountDownLatch(1);
+
+    private static ServiceWatcher sServiceWatcher;
+
+    static {
+        Log.i(TAG, "Starting thread " + sMyThread);
+        sMyThread.start();
+    }
+
+    public CtsAugmentedAutofillService() {
+        mHandler = Handler.createAsync(sMyThread.getLooper());
+    }
+
+    @NonNull
+    public static ServiceWatcher setServiceWatcher() {
+        if (sServiceWatcher != null) {
+            throw new IllegalStateException("There Can Be Only One!");
+        }
+        sServiceWatcher = new ServiceWatcher();
+        return sServiceWatcher;
+    }
+
+
+    public static void resetStaticState() {
+        List<Throwable> exceptions = sAugmentedReplier.mExceptions;
+        if (exceptions != null) {
+            exceptions.clear();
+        }
+        // TODO(b/123540602): should probably set sInstance to null as well, but first we would need
+        // to make sure each test unbinds the service.
+
+        // TODO(b/123540602): each test should use a different service instance, but we need
+        // to provide onConnected() / onDisconnected() methods first and then change the infra so
+        // we can wait for those
+
+        if (sServiceWatcher != null) {
+            Log.wtf(TAG, "resetStaticState(): should not have sServiceWatcher");
+            sServiceWatcher = null;
+        }
+    }
+
+    @Override
+    public void onConnected() {
+        Log.i(TAG, "onConnected(): sServiceWatcher=" + sServiceWatcher);
+
+        if (sServiceWatcher == null) {
+            addException("onConnected() without a watcher");
+            return;
+        }
+
+        if (sServiceWatcher.mService != null) {
+            addException("onConnected(): already created: %s", sServiceWatcher);
+            return;
+        }
+
+        sServiceWatcher.mService = this;
+        sServiceWatcher.mCreated.countDown();
+
+        Log.d(TAG, "Whitelisting " + Helper.MY_PACKAGE + " for augmented autofill");
+        final ArraySet<String> packages = new ArraySet<>(1);
+        packages.add(Helper.MY_PACKAGE);
+
+        final AutofillManager afm = getApplication().getSystemService(AutofillManager.class);
+        if (afm == null) {
+            addException("No AutofillManager on application context on onConnected()");
+            return;
+        }
+        afm.setAugmentedAutofillWhitelist(packages, /* activities= */ null);
+
+        if (mConnectedLatch.getCount() == 0) {
+            addException("already connected: %s", mConnectedLatch);
+        }
+        mConnectedLatch.countDown();
+    }
+
+    @Override
+    public void onDisconnected() {
+        Log.i(TAG, "onDisconnected(): sServiceWatcher=" + sServiceWatcher);
+
+        if (mDisconnectedLatch.getCount() == 0) {
+            addException("already disconnected: %s", mConnectedLatch);
+        }
+        mDisconnectedLatch.countDown();
+
+        if (sServiceWatcher == null) {
+            addException("onDisconnected() without a watcher");
+            return;
+        }
+        if (sServiceWatcher.mService == null) {
+            addException("onDisconnected(): no service on %s", sServiceWatcher);
+            return;
+        }
+
+        sServiceWatcher.mDestroyed.countDown();
+        sServiceWatcher.mService = null;
+        sServiceWatcher = null;
+    }
+
+    public FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
+        return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
+            final FillEventHistory history = getFillEventHistory();
+            if (history == null) {
+                return null;
+            }
+            final List<Event> events = history.getEvents();
+            if (events != null) {
+                assertWithMessage("Didn't get " + expectedSize + " events yet: " + events).that(
+                        events.size()).isEqualTo(expectedSize);
+            } else {
+                assertWithMessage("Events is null (expecting " + expectedSize + ")").that(
+                        expectedSize).isEqualTo(0);
+                return null;
+            }
+            return history;
+        });
+    }
+
+    /**
+     * Waits until the system calls {@link #onConnected()}.
+     */
+    public void waitUntilConnected() throws InterruptedException {
+        await(mConnectedLatch, "not connected");
+    }
+
+    /**
+     * Waits until the system calls {@link #onDisconnected()}.
+     */
+    public void waitUntilDisconnected() throws InterruptedException {
+        await(mDisconnectedLatch, "not disconnected");
+    }
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillController controller, FillCallback callback) {
+        Log.i(TAG, "onFillRequest(): " + AugmentedHelper.toString(request));
+
+        final ComponentName component = request.getActivityComponent();
+
+        if (!TestNameUtils.isRunningTest()) {
+            Log.e(TAG, "onFillRequest(" + component + ") called after tests finished");
+            return;
+        }
+        mHandler.post(() -> sAugmentedReplier.handleOnFillRequest(getApplicationContext(), request,
+                cancellationSignal, controller, callback));
+    }
+
+    /**
+     * Gets the {@link AugmentedReplier} singleton.
+     */
+    public static AugmentedReplier getAugmentedReplier() {
+        return sAugmentedReplier;
+    }
+
+    private static void addException(@NonNull String fmt, @Nullable Object...args) {
+        final String msg = String.format(fmt, args);
+        Log.e(TAG, msg);
+        sAugmentedReplier.addException(new IllegalStateException(msg));
+    }
+
+    /**
+     * POJO representation of the contents of a {@link FillRequest}
+     * that can be asserted at the end of a test case.
+     */
+    public static final class AugmentedFillRequest {
+        public final FillRequest request;
+        public final CancellationSignal cancellationSignal;
+        public final FillController controller;
+        public final FillCallback callback;
+
+        private AugmentedFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+                FillController controller, FillCallback callback) {
+            this.request = request;
+            this.cancellationSignal = cancellationSignal;
+            this.controller = controller;
+            this.callback = callback;
+        }
+
+        @Override
+        public String toString() {
+            return "AugmentedFillRequest[activity=" + getActivityName(request) + ", request="
+                    + AugmentedHelper.toString(request) + "]";
+        }
+    }
+
+    /**
+     * Object used to answer a
+     * {@link AugmentedAutofillService#onFillRequest(FillRequest, CancellationSignal,
+     * FillController, FillCallback)} on behalf of a unit test method.
+     */
+    public static final class AugmentedReplier {
+
+        private final BlockingQueue<CannedAugmentedFillResponse> mResponses =
+                new LinkedBlockingQueue<>();
+        private final BlockingQueue<AugmentedFillRequest> mFillRequests =
+                new LinkedBlockingQueue<>();
+
+        private List<Throwable> mExceptions;
+        private boolean mReportUnhandledFillRequest = true;
+
+        private AugmentedReplier() {
+        }
+
+        /**
+         * Gets the exceptions thrown asynchronously, if any.
+         */
+        @Nullable
+        public List<Throwable> getExceptions() {
+            return mExceptions;
+        }
+
+        private void addException(@Nullable Throwable e) {
+            if (e == null) return;
+
+            if (mExceptions == null) {
+                mExceptions = new ArrayList<>();
+            }
+            mExceptions.add(e);
+        }
+
+        /**
+         * Sets the expectation for the next {@code onFillRequest}.
+         */
+        public AugmentedReplier addResponse(@NonNull CannedAugmentedFillResponse response) {
+            if (response == null) {
+                throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
+            }
+            mResponses.add(response);
+            return this;
+        }
+        /**
+         * Gets the next fill request, in the order received.
+         */
+        public AugmentedFillRequest getNextFillRequest() {
+            AugmentedFillRequest request;
+            try {
+                request = mFillRequests.poll(AUGMENTED_FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Interrupted", e);
+            }
+            if (request == null) {
+                throw new RetryableException(AUGMENTED_FILL_TIMEOUT, "onFillRequest() not called");
+            }
+            return request;
+        }
+
+        /**
+         * Asserts all {@link AugmentedAutofillService#onFillRequest(FillRequest,
+         * CancellationSignal, FillController, FillCallback)} received by the service were properly
+         * {@link #getNextFillRequest() handled} by the test case.
+         */
+        public void assertNoUnhandledFillRequests() {
+            if (mFillRequests.isEmpty()) return; // Good job, test case!
+
+            if (!mReportUnhandledFillRequest) {
+                // Just log, so it's not thrown again on @After if already thrown on main body
+                Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
+                        + "but logging just in case: " + mFillRequests);
+                return;
+            }
+
+            mReportUnhandledFillRequest = false;
+            throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
+                    + mFillRequests);
+        }
+
+        /**
+         * Gets the current number of unhandled requests.
+         */
+        public int getNumberUnhandledFillRequests() {
+            return mFillRequests.size();
+        }
+
+        /**
+         * Resets its internal state.
+         */
+        public void reset() {
+            mResponses.clear();
+            mFillRequests.clear();
+            mExceptions = null;
+            mReportUnhandledFillRequest = true;
+        }
+
+        private void handleOnFillRequest(@NonNull Context context, @NonNull FillRequest request,
+                @NonNull CancellationSignal cancellationSignal, @NonNull FillController controller,
+                @NonNull FillCallback callback) {
+            final AugmentedFillRequest myRequest = new AugmentedFillRequest(request,
+                    cancellationSignal, controller, callback);
+            Log.d(TAG, "offering " + myRequest);
+            Helper.offer(mFillRequests, myRequest, AUGMENTED_CONNECTION_TIMEOUT.ms());
+            try {
+                final CannedAugmentedFillResponse response;
+                try {
+                    response = mResponses.poll(AUGMENTED_CONNECTION_TIMEOUT.ms(),
+                            TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "Interrupted getting CannedAugmentedFillResponse: " + e);
+                    Thread.currentThread().interrupt();
+                    addException(e);
+                    return;
+                }
+                if (response == null) {
+                    Log.w(TAG, "onFillRequest() for " + getActivityName(request)
+                            + " received when no canned response was set.");
+                    return;
+                }
+
+                // sleep for timeout tests.
+                final long delay = response.getDelay();
+                if (delay > 0) {
+                    SystemClock.sleep(response.getDelay());
+                }
+
+                if (response.getResponseType() == NULL) {
+                    Log.d(TAG, "onFillRequest(): replying with null");
+                    callback.onSuccess(null);
+                    return;
+                }
+
+                if (response.getResponseType() == TIMEOUT) {
+                    Log.d(TAG, "onFillRequest(): not replying at all");
+                    return;
+                }
+
+                Log.v(TAG, "onFillRequest(): response = " + response);
+                final FillResponse fillResponse = response.asFillResponse(context, request,
+                        controller);
+                Log.v(TAG, "onFillRequest(): fillResponse = " + fillResponse);
+                callback.onSuccess(fillResponse);
+            } catch (Throwable t) {
+                addException(t);
+            }
+        }
+    }
+
+    public static final class ServiceWatcher {
+
+        private final CountDownLatch mCreated = new CountDownLatch(1);
+        private final CountDownLatch mDestroyed = new CountDownLatch(1);
+
+        private CtsAugmentedAutofillService mService;
+
+        @NonNull
+        public CtsAugmentedAutofillService waitOnConnected() throws InterruptedException {
+            await(mCreated, "not created");
+
+            if (mService == null) {
+                throw new IllegalStateException("not created");
+            }
+
+            return mService;
+        }
+
+        public void waitOnDisconnected() throws InterruptedException {
+            await(mDestroyed, "not destroyed");
+        }
+
+        @Override
+        public String toString() {
+            return "mService: " + mService + " created: " + (mCreated.getCount() == 0)
+                    + " destroyed: " + (mDestroyed.getCount() == 0);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CustomDescriptionHelper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CustomDescriptionHelper.java
new file mode 100644
index 0000000..be88bf6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CustomDescriptionHelper.java
@@ -0,0 +1,50 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.autofillservice.cts.R;
+import android.service.autofill.CustomDescription;
+import android.widget.RemoteViews;
+
+public final class CustomDescriptionHelper {
+
+    public static final String ID_SHOW = "show";
+    public static final String ID_HIDE = "hide";
+    public static final String ID_USERNAME_PLAIN = "username_plain";
+    public static final String ID_USERNAME_MASKED = "username_masked";
+    public static final String ID_PASSWORD_PLAIN = "password_plain";
+    public static final String ID_PASSWORD_MASKED = "password_masked";
+
+    private static final String sPackageName =
+            getInstrumentation().getTargetContext().getPackageName();
+
+
+    public static CustomDescription.Builder newCustomDescriptionWithUsernameAndPassword() {
+        return new CustomDescription.Builder(new RemoteViews(sPackageName,
+                R.layout.custom_description_with_username_and_password));
+    }
+
+    public static CustomDescription.Builder newCustomDescriptionWithHiddenFields() {
+        return new CustomDescription.Builder(new RemoteViews(sPackageName,
+                R.layout.custom_description_with_hidden_fields));
+    }
+
+    private CustomDescriptionHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/DismissType.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/DismissType.java
new file mode 100644
index 0000000..97a053a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/DismissType.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+/**
+ * A simple enum for test cases where the Save UI is dismissed.
+ *
+ * <p><b>Note:</b> When new values are added to the enum, the equivalent tests must be added to
+ * both {@link LoginActivityTest} and {@link SimpleSaveActivityTest}.
+ */
+public enum DismissType {
+    BACK_BUTTON,
+    HOME_BUTTON,
+    TOUCH_OUTSIDE,
+    FOCUS_OUTSIDE
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/DoubleVisitor.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/DoubleVisitor.java
new file mode 100644
index 0000000..9f788d7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/DoubleVisitor.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Implements the Visitor design pattern to visit 2 related objects (like a view and the activity
+ * hosting it).
+ *
+ * @param <V1> 1st visited object
+ * @param <V2> 2nd visited object
+ */
+// TODO: move to common
+public interface DoubleVisitor<V1, V2> {
+
+    /**
+     * Visit those objects.
+     */
+    void visit(@NonNull V1 visited1, @NonNull V2 visited2);
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
new file mode 100644
index 0000000..8cd3813
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -0,0 +1,1608 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.UiBot.PORTRAIT;
+import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
+import static android.provider.Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
+import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASETS_SHOWN;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
+import android.autofillservice.cts.R;
+import android.content.AutofillOptions;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.icu.util.Calendar;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.Settings;
+import android.service.autofill.FieldClassification;
+import android.service.autofill.FieldClassification.Match;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.InlinePresentation;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStructure.HtmlInfo;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.AutofillCallback;
+import android.view.autofill.AutofillValue;
+import android.webkit.WebView;
+import android.widget.RemoteViews;
+import android.widget.inline.InlinePresentationSpec;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.autofill.inline.v1.InlineSuggestionUi;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.BitmapUtils;
+import com.android.compatibility.common.util.OneTimeSettingsListener;
+import com.android.compatibility.common.util.SettingsUtils;
+import com.android.compatibility.common.util.ShellUtils;
+import com.android.compatibility.common.util.TestNameUtils;
+import com.android.compatibility.common.util.Timeout;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+/**
+ * Helper for common funcionalities.
+ */
+public final class Helper {
+
+    public static final String TAG = "AutoFillCtsHelper";
+
+    public static final boolean VERBOSE = false;
+
+    public static final String MY_PACKAGE = "android.autofillservice.cts";
+
+    public static final String ID_USERNAME_LABEL = "username_label";
+    public static final String ID_USERNAME = "username";
+    public static final String ID_PASSWORD_LABEL = "password_label";
+    public static final String ID_PASSWORD = "password";
+    public static final String ID_LOGIN = "login";
+    public static final String ID_OUTPUT = "output";
+    public static final String ID_STATIC_TEXT = "static_text";
+    public static final String ID_EMPTY = "empty";
+    public static final String ID_CANCEL_FILL = "cancel_fill";
+
+    public static final String NULL_DATASET_ID = null;
+
+    public static final char LARGE_STRING_CHAR = '6';
+    // NOTE: cannot be much large as it could ANR and fail the test.
+    public static final int LARGE_STRING_SIZE = 100_000;
+    public static final String LARGE_STRING = com.android.compatibility.common.util.TextUtils
+            .repeat(LARGE_STRING_CHAR, LARGE_STRING_SIZE);
+
+    /**
+     * Can be used in cases where the autofill values is required by irrelevant (like adding a
+     * value to an authenticated dataset).
+     */
+    public static final String UNUSED_AUTOFILL_VALUE = null;
+
+    private static final String ACCELLEROMETER_CHANGE =
+            "content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
+                    + "--bind value:i:%d";
+
+    private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory()
+            + "/CtsAutoFillServiceTestCases";
+
+    private static final Timeout SETTINGS_BASED_SHELL_CMD_TIMEOUT = new Timeout(
+            "SETTINGS_SHELL_CMD_TIMEOUT", OneTimeSettingsListener.DEFAULT_TIMEOUT_MS / 2, 2,
+            OneTimeSettingsListener.DEFAULT_TIMEOUT_MS);
+
+    /**
+     * Helper interface used to filter nodes.
+     *
+     * @param <T> node type
+     */
+    interface NodeFilter<T> {
+        /**
+         * Returns whether the node passes the filter for such given id.
+         */
+        boolean matches(T node, Object id);
+    }
+
+    private static final NodeFilter<ViewNode> RESOURCE_ID_FILTER = (node, id) -> {
+        return id.equals(node.getIdEntry());
+    };
+
+    private static final NodeFilter<ViewNode> HTML_NAME_FILTER = (node, id) -> {
+        return id.equals(getHtmlName(node));
+    };
+
+    private static final NodeFilter<ViewNode> HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> {
+        return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry());
+    };
+
+    private static final NodeFilter<ViewNode> TEXT_FILTER = (node, id) -> {
+        return id.equals(node.getText());
+    };
+
+    private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> {
+        return hasHint(node.getAutofillHints(), id);
+    };
+
+    private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> {
+        final String className = node.getClassName();
+        if (!className.equals("android.webkit.WebView")) return false;
+
+        final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form");
+        final String formName = getAttributeValue(htmlInfo, "name");
+        return id.equals(formName);
+    };
+
+    private static final NodeFilter<View> AUTOFILL_HINT_VIEW_FILTER = (view, id) -> {
+        return hasHint(view.getAutofillHints(), id);
+    };
+
+    private static String toString(AssistStructure structure, StringBuilder builder) {
+        builder.append("[component=").append(structure.getActivityComponent());
+        final int nodes = structure.getWindowNodeCount();
+        for (int i = 0; i < nodes; i++) {
+            final WindowNode windowNode = structure.getWindowNodeAt(i);
+            dump(builder, windowNode.getRootViewNode(), " ", 0);
+        }
+        return builder.append(']').toString();
+    }
+
+    @NonNull
+    public static String toString(@NonNull AssistStructure structure) {
+        return toString(structure, new StringBuilder());
+    }
+
+    @Nullable
+    public static String toString(@Nullable AutofillValue value) {
+        if (value == null) return null;
+        if (value.isText()) {
+            // We don't care about PII...
+            final CharSequence text = value.getTextValue();
+            return text == null ? null : text.toString();
+        }
+        return value.toString();
+    }
+
+    /**
+     * Dump the assist structure on logcat.
+     */
+    public static void dumpStructure(String message, AssistStructure structure) {
+        Log.i(TAG, toString(structure, new StringBuilder(message)));
+    }
+
+    /**
+     * Dump the contexts on logcat.
+     */
+    public static void dumpStructure(String message, List<FillContext> contexts) {
+        for (FillContext context : contexts) {
+            dumpStructure(message, context.getStructure());
+        }
+    }
+
+    /**
+     * Dumps the state of the autofill service on logcat.
+     */
+    public static void dumpAutofillService(@NonNull String tag) {
+        final String autofillDump = runShellCommand("dumpsys autofill");
+        Log.i(tag, "dumpsys autofill\n\n" + autofillDump);
+        final String myServiceDump = runShellCommand("dumpsys activity service %s",
+                InstrumentedAutoFillService.SERVICE_NAME);
+        Log.i(tag, "my service dump: \n" + myServiceDump);
+    }
+
+    /**
+     * Dumps the state of {@link android.service.autofill.InlineSuggestionRenderService}, and assert
+     * that it says the number of active inline suggestion views is the given number.
+     *
+     * <p>Note that ideally we should have a test api to fetch the number and verify against it.
+     * But at the time this test is added for Android 11, we have passed the deadline for adding
+     * the new test api, hence this approach.
+     */
+    public static void assertActiveViewCountFromInlineSuggestionRenderService(int count) {
+        String response = runShellCommand(
+                "dumpsys activity service .InlineSuggestionRenderService");
+        Log.d(TAG, "InlineSuggestionRenderService dump: " + response);
+        Pattern pattern = Pattern.compile(".*mActiveInlineSuggestions: " + count + ".*");
+        assertWithMessage("Expecting view count " + count
+                + ", but seeing different count from service dumpsys " + response).that(
+                pattern.matcher(response).find()).isTrue();
+    }
+
+    /**
+     * Sets whether the user completed the initial setup.
+     */
+    public static void setUserComplete(Context context, boolean complete) {
+        SettingsUtils.syncSet(context, USER_SETUP_COMPLETE, complete ? "1" : null);
+    }
+
+    private static void dump(@NonNull StringBuilder builder, @NonNull ViewNode node,
+            @NonNull String prefix, int childId) {
+        final int childrenSize = node.getChildCount();
+        builder.append("\n").append(prefix)
+            .append("child #").append(childId).append(':');
+        append(builder, "afId", node.getAutofillId());
+        append(builder, "afType", node.getAutofillType());
+        append(builder, "afValue", toString(node.getAutofillValue()));
+        append(builder, "resId", node.getIdEntry());
+        append(builder, "class", node.getClassName());
+        append(builder, "text", node.getText());
+        append(builder, "webDomain", node.getWebDomain());
+        append(builder, "checked", node.isChecked());
+        append(builder, "focused", node.isFocused());
+        final HtmlInfo htmlInfo = node.getHtmlInfo();
+        if (htmlInfo != null) {
+            builder.append(", HtmlInfo[tag=").append(htmlInfo.getTag())
+                .append(", attrs: ").append(htmlInfo.getAttributes()).append(']');
+        }
+        if (childrenSize > 0) {
+            append(builder, "#children", childrenSize).append("\n").append(prefix);
+            prefix += " ";
+            if (childrenSize > 0) {
+                for (int i = 0; i < childrenSize; i++) {
+                    dump(builder, node.getChildAt(i), prefix, i);
+                }
+            }
+        }
+    }
+
+    /**
+     * Appends a field value to a {@link StringBuilder} when it's not {@code null}.
+     */
+    @NonNull
+    public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
+            @Nullable Object value) {
+        if (value == null) return builder;
+
+        if ((value instanceof Boolean) && ((Boolean) value)) {
+            return builder.append(", ").append(field);
+        }
+
+        if (value instanceof Integer && ((Integer) value) == 0
+                || value instanceof CharSequence && TextUtils.isEmpty((CharSequence) value)) {
+            return builder;
+        }
+
+        return builder.append(", ").append(field).append('=').append(value);
+    }
+
+    /**
+     * Appends a field value to a {@link StringBuilder} when it's {@code true}.
+     */
+    @NonNull
+    public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
+            boolean value) {
+        if (value) {
+            builder.append(", ").append(field);
+        }
+        return builder;
+    }
+
+    /**
+     * Gets a node if it matches the filter criteria for the given id.
+     */
+    public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
+            @NonNull NodeFilter<ViewNode> filter) {
+        Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
+        final int nodes = structure.getWindowNodeCount();
+        for (int i = 0; i < nodes; i++) {
+            final WindowNode windowNode = structure.getWindowNodeAt(i);
+            final ViewNode rootNode = windowNode.getRootViewNode();
+            final ViewNode node = findNodeByFilter(rootNode, id, filter);
+            if (node != null) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets a node if it matches the filter criteria for the given id.
+     */
+    public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
+            @NonNull NodeFilter<ViewNode> filter) {
+        for (FillContext context : contexts) {
+            ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
+            if (node != null) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets a node if it matches the filter criteria for the given id.
+     */
+    public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
+            @NonNull NodeFilter<ViewNode> filter) {
+        if (filter.matches(node, id)) {
+            return node;
+        }
+        final int childrenSize = node.getChildCount();
+        if (childrenSize > 0) {
+            for (int i = 0; i < childrenSize; i++) {
+                final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter);
+                if (found != null) {
+                    return found;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets a node given its Android resource id, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) {
+        return findNodeByFilter(structure, resourceId, RESOURCE_ID_FILTER);
+    }
+
+    /**
+     * Gets a node given its Android resource id, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
+        return findNodeByFilter(contexts, resourceId, RESOURCE_ID_FILTER);
+    }
+
+    /**
+     * Gets a node given its Android resource id, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByResourceId(ViewNode node, String resourceId) {
+        return findNodeByFilter(node, resourceId, RESOURCE_ID_FILTER);
+    }
+
+    /**
+     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) {
+        return findNodeByFilter(structure, htmlName, HTML_NAME_FILTER);
+    }
+
+    /**
+     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) {
+        return findNodeByFilter(contexts, htmlName, HTML_NAME_FILTER);
+    }
+
+    /**
+     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) {
+        return findNodeByFilter(node, htmlName, HTML_NAME_FILTER);
+    }
+
+    /**
+     * Gets a node given the value of its (single) autofill hint property, or {@code null} if not
+     * found.
+     */
+    public static ViewNode findNodeByAutofillHint(ViewNode node, String hint) {
+        return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER);
+    }
+
+    /**
+     * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
+     * not found.
+     */
+    public static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) {
+        return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER);
+    }
+
+    /**
+     * Gets a node given its Android resource id.
+     */
+    @NonNull
+    public static AutofillId findAutofillIdByResourceId(@NonNull FillContext context,
+            @NonNull String resourceId) {
+        final ViewNode node = findNodeByFilter(context.getStructure(), resourceId,
+                RESOURCE_ID_FILTER);
+        assertWithMessage("No node for resourceId %s", resourceId).that(node).isNotNull();
+        return node.getAutofillId();
+    }
+
+    /**
+     * Gets the {@code name} attribute of a node representing an HTML input tag.
+     */
+    @Nullable
+    public static String getHtmlName(@NonNull ViewNode node) {
+        final HtmlInfo htmlInfo = node.getHtmlInfo();
+        if (htmlInfo == null) {
+            return null;
+        }
+        final String tag = htmlInfo.getTag();
+        if (!"input".equals(tag)) {
+            Log.w(TAG, "getHtmlName(): invalid tag (" + tag + ") on " + htmlInfo);
+            return null;
+        }
+        for (Pair<String, String> attr : htmlInfo.getAttributes()) {
+            if ("name".equals(attr.first)) {
+                return attr.second;
+            }
+        }
+        Log.w(TAG, "getHtmlName(): no 'name' attribute on " + htmlInfo);
+        return null;
+    }
+
+    /**
+     * Gets a node given its expected text, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByText(AssistStructure structure, String text) {
+        return findNodeByFilter(structure, text, TEXT_FILTER);
+    }
+
+    /**
+     * Gets a node given its expected text, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByText(ViewNode node, String text) {
+        return findNodeByFilter(node, text, TEXT_FILTER);
+    }
+
+    /**
+     * Gets a view that contains the an autofill hint, or {@code null} if not found.
+     */
+    public static View findViewByAutofillHint(Activity activity, String hint) {
+        final View rootView = activity.getWindow().getDecorView().getRootView();
+        return findViewByAutofillHint(rootView, hint);
+    }
+
+    /**
+     * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if
+     * not found.
+     */
+    public static View findViewByAutofillHint(View view, String hint) {
+        if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view;
+        if ((view instanceof ViewGroup)) {
+            final ViewGroup group = (ViewGroup) view;
+            for (int i = 0; i < group.getChildCount(); i++) {
+                final View child = findViewByAutofillHint(group.getChildAt(i), hint);
+                if (child != null) return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Asserts a text-based node is sanitized.
+     */
+    public static void assertTextIsSanitized(ViewNode node) {
+        final CharSequence text = node.getText();
+        final String resourceId = node.getIdEntry();
+        if (!TextUtils.isEmpty(text)) {
+            throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
+        }
+
+        assertNotFromResources(node);
+        assertNodeHasNoAutofillValue(node);
+    }
+
+    private static void assertNotFromResources(ViewNode node) {
+        assertThat(node.getTextIdEntry()).isNull();
+    }
+
+    public static void assertNodeHasNoAutofillValue(ViewNode node) {
+        final AutofillValue value = node.getAutofillValue();
+        if (value != null) {
+            final String text = value.isText() ? value.getTextValue().toString() : "N/A";
+            throw new AssertionError("node has value: " + value + " text=" + text);
+        }
+    }
+
+    /**
+     * Asserts the contents of a text-based node that is also auto-fillable.
+     */
+    public static void assertTextOnly(ViewNode node, String expectedValue) {
+        assertText(node, expectedValue, false);
+        assertNotFromResources(node);
+    }
+
+    /**
+     * Asserts the contents of a text-based node that is also auto-fillable.
+     */
+    public static void assertTextOnly(AssistStructure structure, String resourceId,
+            String expectedValue) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertText(node, expectedValue, false);
+        assertNotFromResources(node);
+    }
+
+    /**
+     * Asserts the contents of a text-based node that is also auto-fillable.
+     */
+    public static void assertTextAndValue(ViewNode node, String expectedValue) {
+        assertText(node, expectedValue, true);
+        assertNotFromResources(node);
+    }
+
+    /**
+     * Asserts a text-based node exists and verify its values.
+     */
+    public static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
+            String expectedValue) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertTextAndValue(node, expectedValue);
+        return node;
+    }
+
+    /**
+     * Asserts a text-based node exists and is sanitized.
+     */
+    public static ViewNode assertValue(AssistStructure structure, String resourceId,
+            String expectedValue) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertTextValue(node, expectedValue);
+        return node;
+    }
+
+    /**
+     * Asserts the values of a text-based node whose string come from resoruces.
+     */
+    public static ViewNode assertTextFromResources(AssistStructure structure, String resourceId,
+            String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertText(node, expectedValue, isAutofillable);
+        assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry);
+        return node;
+    }
+
+    public static ViewNode assertHintFromResources(AssistStructure structure, String resourceId,
+            String expectedValue, String expectedHintIdEntry) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertThat(node.getHint()).isEqualTo(expectedValue);
+        assertThat(node.getHintIdEntry()).isEqualTo(expectedHintIdEntry);
+        return node;
+    }
+
+    private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
+        assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString())
+                .isEqualTo(expectedValue);
+        final AutofillValue value = node.getAutofillValue();
+        final AutofillId id = node.getAutofillId();
+        if (isAutofillable) {
+            assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull();
+            assertWithMessage("wrong auto-fill value on %s", id)
+                    .that(value.getTextValue().toString()).isEqualTo(expectedValue);
+        } else {
+            assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull();
+        }
+    }
+
+    /**
+     * Asserts the auto-fill value of a text-based node.
+     */
+    public static ViewNode assertTextValue(ViewNode node, String expectedText) {
+        final AutofillValue value = node.getAutofillValue();
+        final AutofillId id = node.getAutofillId();
+        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue();
+        assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString())
+                .isEqualTo(expectedText);
+        return node;
+    }
+
+    /**
+     * Asserts the auto-fill value of a list-based node.
+     */
+    public static ViewNode assertListValue(ViewNode node, int expectedIndex) {
+        final AutofillValue value = node.getAutofillValue();
+        final AutofillId id = node.getAutofillId();
+        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue();
+        assertWithMessage("wrong autofill value on %s", id).that(value.getListValue())
+                .isEqualTo(expectedIndex);
+        return node;
+    }
+
+    /**
+     * Asserts the auto-fill value of a toggle-based node.
+     */
+    public static void assertToggleValue(ViewNode node, boolean expectedToggle) {
+        final AutofillValue value = node.getAutofillValue();
+        final AutofillId id = node.getAutofillId();
+        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue();
+        assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue())
+                .isEqualTo(expectedToggle);
+    }
+
+    /**
+     * Asserts the auto-fill value of a date-based node.
+     */
+    public static void assertDateValue(Object object, AutofillValue value, int year, int month,
+            int day) {
+        assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
+
+        final Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(value.getDateValue());
+
+        assertWithMessage("Wrong year on AutofillValue %s", value)
+            .that(cal.get(Calendar.YEAR)).isEqualTo(year);
+        assertWithMessage("Wrong month on AutofillValue %s", value)
+            .that(cal.get(Calendar.MONTH)).isEqualTo(month);
+        assertWithMessage("Wrong day on AutofillValue %s", value)
+             .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day);
+    }
+
+    /**
+     * Asserts the auto-fill value of a date-based node.
+     */
+    public static void assertDateValue(ViewNode node, int year, int month, int day) {
+        assertDateValue(node, node.getAutofillValue(), year, month, day);
+    }
+
+    /**
+     * Asserts the auto-fill value of a date-based view.
+     */
+    public static void assertDateValue(View view, int year, int month, int day) {
+        assertDateValue(view, view.getAutofillValue(), year, month, day);
+    }
+
+    /**
+     * Asserts the auto-fill value of a time-based node.
+     */
+    private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) {
+        assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
+
+        final Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(value.getDateValue());
+
+        assertWithMessage("Wrong hour on AutofillValue %s", value)
+            .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour);
+        assertWithMessage("Wrong minute on AutofillValue %s", value)
+            .that(cal.get(Calendar.MINUTE)).isEqualTo(minute);
+    }
+
+    /**
+     * Asserts the auto-fill value of a time-based node.
+     */
+    public static void assertTimeValue(ViewNode node, int hour, int minute) {
+        assertTimeValue(node, node.getAutofillValue(), hour, minute);
+    }
+
+    /**
+     * Asserts the auto-fill value of a time-based view.
+     */
+    public static void assertTimeValue(View view, int hour, int minute) {
+        assertTimeValue(view, view.getAutofillValue(), hour, minute);
+    }
+
+    /**
+     * Asserts a text-based node exists and is sanitized.
+     */
+    public static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
+        assertTextIsSanitized(node);
+        return node;
+    }
+
+    /**
+     * Asserts a list-based node exists and is sanitized.
+     */
+    public static void assertListValueIsSanitized(AssistStructure structure, String resourceId) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
+        assertTextIsSanitized(node);
+    }
+
+    /**
+     * Asserts a toggle node exists and is sanitized.
+     */
+    public static void assertToggleIsSanitized(AssistStructure structure, String resourceId) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertNodeHasNoAutofillValue(node);
+        assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked())
+                .isFalse();
+    }
+
+    /**
+     * Asserts a node exists and has the {@code expected} number of children.
+     */
+    public static void assertNumberOfChildren(AssistStructure structure, String resourceId,
+            int expected) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        final int actual = node.getChildCount();
+        if (actual != expected) {
+            dumpStructure("assertNumberOfChildren()", structure);
+            throw new AssertionError("assertNumberOfChildren() for " + resourceId
+                    + " failed: expected " + expected + ", got " + actual);
+        }
+    }
+
+    /**
+     * Asserts the number of children in the Assist structure.
+     */
+    public static void assertNumberOfChildren(AssistStructure structure, int expected) {
+        assertWithMessage("wrong number of nodes").that(structure.getWindowNodeCount())
+                .isEqualTo(1);
+        final int actual = getNumberNodes(structure);
+        if (actual != expected) {
+            dumpStructure("assertNumberOfChildren()", structure);
+            throw new AssertionError("assertNumberOfChildren() for structure failed: expected "
+                    + expected + ", got " + actual);
+        }
+    }
+
+    /**
+     * Gets the total number of nodes in an structure.
+     */
+    public static int getNumberNodes(AssistStructure structure) {
+        int count = 0;
+        final int nodes = structure.getWindowNodeCount();
+        for (int i = 0; i < nodes; i++) {
+            final WindowNode windowNode = structure.getWindowNodeAt(i);
+            final ViewNode rootNode = windowNode.getRootViewNode();
+            count += getNumberNodes(rootNode);
+        }
+        return count;
+    }
+
+    /**
+     * Gets the total number of nodes in an node, including all descendants and the node itself.
+     */
+    public static int getNumberNodes(ViewNode node) {
+        int count = 1;
+        final int childrenSize = node.getChildCount();
+        if (childrenSize > 0) {
+            for (int i = 0; i < childrenSize; i++) {
+                count += getNumberNodes(node.getChildAt(i));
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given
+     * {@code resourceIds}.
+     */
+    public static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver,
+            String[] resourceIds) {
+        if (resourceIds == null) return null;
+
+        final AutofillId[] requiredIds = new AutofillId[resourceIds.length];
+        for (int i = 0; i < resourceIds.length; i++) {
+            final String resourceId = resourceIds[i];
+            final ViewNode node = nodeResolver.apply(resourceId);
+            if (node == null) {
+                throw new AssertionError("No node with resourceId " + resourceId);
+            }
+            requiredIds[i] = node.getAutofillId();
+
+        }
+        return requiredIds;
+    }
+
+    /**
+     * Get an {@link AutofillId} mapped from the {@code structure} node with the given
+     * {@code resourceId}.
+     */
+    public static AutofillId getAutofillId(Function<String, ViewNode> nodeResolver,
+            String resourceId) {
+        if (resourceId == null) return null;
+
+        final ViewNode node = nodeResolver.apply(resourceId);
+        if (node == null) {
+            throw new AssertionError("No node with resourceId " + resourceId);
+        }
+        return node.getAutofillId();
+    }
+
+    /**
+     * Prevents the screen to rotate by itself
+     */
+    public static void disableAutoRotation(UiBot uiBot) throws Exception {
+        runShellCommand(ACCELLEROMETER_CHANGE, 0);
+        uiBot.setScreenOrientation(PORTRAIT);
+    }
+
+    /**
+     * Allows the screen to rotate by itself
+     */
+    public static void allowAutoRotation() {
+        runShellCommand(ACCELLEROMETER_CHANGE, 1);
+    }
+
+    /**
+     * Gets the maximum number of partitions per session.
+     */
+    public static int getMaxPartitions() {
+        return Integer.parseInt(runShellCommand("cmd autofill get max_partitions"));
+    }
+
+    /**
+     * Sets the maximum number of partitions per session.
+     */
+    public static void setMaxPartitions(int value) throws Exception {
+        runShellCommand("cmd autofill set max_partitions %d", value);
+        SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_partitions", () -> {
+            return getMaxPartitions() == value ? Boolean.TRUE : null;
+        });
+    }
+
+    /**
+     * Gets the maximum number of visible datasets.
+     */
+    public static int getMaxVisibleDatasets() {
+        return Integer.parseInt(runShellCommand("cmd autofill get max_visible_datasets"));
+    }
+
+    /**
+     * Sets the maximum number of visible datasets.
+     */
+    public static void setMaxVisibleDatasets(int value) throws Exception {
+        runShellCommand("cmd autofill set max_visible_datasets %d", value);
+        SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_visible_datasets", () -> {
+            return getMaxVisibleDatasets() == value ? Boolean.TRUE : null;
+        });
+    }
+
+    /**
+     * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi.
+     */
+    public static boolean isAutofillWindowFullScreen(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
+    /**
+     * Checks if screen orientation can be changed.
+     */
+    public static boolean isRotationSupported(Context context) {
+        final PackageManager packageManager = context.getPackageManager();
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            Log.v(TAG, "isRotationSupported(): is auto");
+            return false;
+        }
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+            Log.v(TAG, "isRotationSupported(): has leanback feature");
+            return false;
+        }
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) {
+            Log.v(TAG, "isRotationSupported(): is PC");
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean getBoolean(Context context, String id) {
+        final Resources resources = context.getResources();
+        final int booleanId = resources.getIdentifier(id, "bool", "android");
+        return resources.getBoolean(booleanId);
+    }
+
+    /**
+     * Uses Shell command to get the Autofill logging level.
+     */
+    public static String getLoggingLevel() {
+        return runShellCommand("cmd autofill get log_level");
+    }
+
+    /**
+     * Uses Shell command to set the Autofill logging level.
+     */
+    public static void setLoggingLevel(String level) {
+        runShellCommand("cmd autofill set log_level %s", level);
+    }
+
+    /**
+     * Uses Settings to enable the given autofill service for the default user, and checks the
+     * value was properly check, throwing an exception if it was not.
+     */
+    public static void enableAutofillService(@NonNull Context context,
+            @NonNull String serviceName) {
+        if (isAutofillServiceEnabled(serviceName)) return;
+
+        // Sets the setting synchronously. Note that the config itself is sets synchronously but
+        // launch of the service is asynchronous after the config is updated.
+        SettingsUtils.syncSet(context, AUTOFILL_SERVICE, serviceName);
+
+        // Waits until the service is actually enabled.
+        try {
+            Timeouts.CONNECTION_TIMEOUT.run("Enabling Autofill service", () -> {
+                return isAutofillServiceEnabled(serviceName) ? serviceName : null;
+            });
+        } catch (Exception e) {
+            throw new AssertionError("Enabling Autofill service failed.");
+        }
+    }
+
+    /**
+     * Uses Settings to disable the given autofill service for the default user, and waits until
+     * the setting is deleted.
+     */
+    public static void disableAutofillService(@NonNull Context context) {
+        final String currentService = SettingsUtils.get(AUTOFILL_SERVICE);
+        if (currentService == null) {
+            Log.v(TAG, "disableAutofillService(): already disabled");
+            return;
+        }
+        Log.v(TAG, "Disabling " + currentService);
+        SettingsUtils.syncDelete(context, AUTOFILL_SERVICE);
+    }
+
+    /**
+     * Checks whether the given service is set as the autofill service for the default user.
+     */
+    public static boolean isAutofillServiceEnabled(@NonNull String serviceName) {
+        final String actualName = getAutofillServiceName();
+        return serviceName.equals(actualName);
+    }
+
+    /**
+     * Gets then name of the autofill service for the default user.
+     */
+    public static String getAutofillServiceName() {
+        return SettingsUtils.get(AUTOFILL_SERVICE);
+    }
+
+    /**
+     * Asserts whether the given service is enabled as the autofill service for the default user.
+     */
+    public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) {
+        final String actual = SettingsUtils.get(AUTOFILL_SERVICE);
+        final String expected = enabled ? serviceName : null;
+        assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE)
+                .that(actual).isEqualTo(expected);
+    }
+
+    /**
+     * Enables / disables the default augmented autofill service.
+     */
+    public static void setDefaultAugmentedAutofillServiceEnabled(boolean enabled) {
+        Log.d(TAG, "setDefaultAugmentedAutofillServiceEnabled(): " + enabled);
+        runShellCommand("cmd autofill set default-augmented-service-enabled 0 %s",
+                Boolean.toString(enabled));
+    }
+
+    /**
+     * Gets the instrumentation context.
+     */
+    public static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    /**
+     * Asserts the node has an {@code HTMLInfo} property, with the given tag.
+     */
+    public static HtmlInfo assertHasHtmlTag(ViewNode node, String expectedTag) {
+        final HtmlInfo info = node.getHtmlInfo();
+        assertWithMessage("node doesn't have htmlInfo").that(info).isNotNull();
+        assertWithMessage("wrong tag").that(info.getTag()).isEqualTo(expectedTag);
+        return info;
+    }
+
+    /**
+     * Gets the value of an {@code HTMLInfo} attribute.
+     */
+    @Nullable
+    public static String getAttributeValue(HtmlInfo info, String attribute) {
+        for (Pair<String, String> pair : info.getAttributes()) {
+            if (pair.first.equals(attribute)) {
+                return pair.second;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Asserts a {@code HTMLInfo} has an attribute with a given value.
+     */
+    public static void assertHasAttribute(HtmlInfo info, String attribute, String expectedValue) {
+        final String actualValue = getAttributeValue(info, attribute);
+        assertWithMessage("Attribute %s not found", attribute).that(actualValue).isNotNull();
+        assertWithMessage("Wrong value for Attribute %s", attribute)
+            .that(actualValue).isEqualTo(expectedValue);
+    }
+
+    /**
+     * Finds a {@link WebView} node given its expected form name.
+     */
+    public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) {
+        return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER);
+    }
+
+    private static void assertClientState(Object container, Bundle clientState,
+            String key, String value) {
+        assertWithMessage("'%s' should have client state", container)
+            .that(clientState).isNotNull();
+        assertWithMessage("Wrong number of client state extras on '%s'", container)
+            .that(clientState.keySet().size()).isEqualTo(1);
+        assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container)
+            .that(clientState.getString(key)).isEqualTo(value);
+    }
+
+    /**
+     * Asserts the content of a {@link FillEventHistory#getClientState()}.
+     *
+     * @param history event to be asserted
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    @SuppressWarnings("javadoc")
+    public static void assertDeprecatedClientState(@NonNull FillEventHistory history,
+            @NonNull String key, @NonNull String value) {
+        assertThat(history).isNotNull();
+        @SuppressWarnings("deprecation")
+        final Bundle clientState = history.getClientState();
+        assertClientState(history, clientState, key, value);
+    }
+
+    /**
+     * Asserts the {@link FillEventHistory#getClientState()} is not set.
+     *
+     * @param history event to be asserted
+     */
+    @SuppressWarnings("javadoc")
+    public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) {
+        assertThat(history).isNotNull();
+        @SuppressWarnings("deprecation")
+        final Bundle clientState = history.getClientState();
+        assertWithMessage("History '%s' should not have client state", history)
+             .that(clientState).isNull();
+    }
+
+    /**
+     * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}.
+     *
+     * @param event event to be asserted
+     * @param eventType expected type
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't
+     * have client state)
+     * @param value the only value expected in the client state bundle (or {@code null} if it
+     * shouldn't have client state)
+     * @param fieldClassificationResults expected results when asserting field classification
+     */
+    private static void assertFillEvent(@NonNull FillEventHistory.Event event,
+            int eventType, @Nullable String datasetId,
+            @Nullable String key, @Nullable String value,
+            @Nullable FieldClassificationResult[] fieldClassificationResults) {
+        assertThat(event).isNotNull();
+        assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType);
+        if (datasetId == null) {
+            assertWithMessage("Event %s should not have dataset id", event)
+                .that(event.getDatasetId()).isNull();
+        } else {
+            assertWithMessage("Wrong dataset id for %s", event)
+                .that(event.getDatasetId()).isEqualTo(datasetId);
+        }
+        final Bundle clientState = event.getClientState();
+        if (key == null) {
+            assertWithMessage("Event '%s' should not have client state", event)
+                .that(clientState).isNull();
+        } else {
+            assertClientState(event, clientState, key, value);
+        }
+        assertWithMessage("Event '%s' should not have selected datasets", event)
+                .that(event.getSelectedDatasetIds()).isEmpty();
+        assertWithMessage("Event '%s' should not have ignored datasets", event)
+                .that(event.getIgnoredDatasetIds()).isEmpty();
+        assertWithMessage("Event '%s' should not have changed fields", event)
+                .that(event.getChangedFields()).isEmpty();
+        assertWithMessage("Event '%s' should not have manually-entered fields", event)
+                .that(event.getManuallyEnteredField()).isEmpty();
+        final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification();
+        if (fieldClassificationResults == null) {
+            assertThat(detectedFields).isEmpty();
+        } else {
+            assertThat(detectedFields).hasSize(fieldClassificationResults.length);
+            int i = 0;
+            for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) {
+                assertMatches(i, entry, fieldClassificationResults[i]);
+                i++;
+            }
+        }
+    }
+
+    private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult,
+            FieldClassificationResult expectedResult) {
+        assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey())
+                .isEqualTo(expectedResult.id);
+        final List<Match> matches = actualResult.getValue().getMatches();
+        assertWithMessage("Wrong number of matches: " + matches).that(matches.size())
+                .isEqualTo(expectedResult.categoryIds.length);
+        for (int j = 0; j < matches.size(); j++) {
+            final Match match = matches.get(j);
+            assertWithMessage("Wrong categoryId at (%s, %s): %s", i, j, match)
+                .that(match.getCategoryId()).isEqualTo(expectedResult.categoryIds[j]);
+            assertWithMessage("Wrong score at (%s, %s): %s", i, j, match)
+                .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]);
+        }
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     */
+    public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
+            @Nullable String datasetId) {
+        assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
+            @Nullable String datasetId, @Nullable String key, @Nullable String value) {
+        assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
+            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     */
+    public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
+            @Nullable String datasetId) {
+        assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
+     *
+     * @param event event to be asserted
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
+            @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
+     *
+     * @param event event to be asserted
+     */
+    public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event) {
+        assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED}
+     * event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForDatasetAuthenticationSelected(
+            @NonNull FillEventHistory.Event event,
+            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForAuthenticationSelected(
+            @NonNull FillEventHistory.Event event,
+            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+    }
+
+    public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
+            @NonNull AutofillId fieldId, @NonNull String categoryId, float score) {
+        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null,
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId, categoryId, score)
+                });
+    }
+
+    public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
+            @NonNull FieldClassificationResult[] results) {
+        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results);
+    }
+
+    public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) {
+        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null);
+    }
+
+    @NonNull
+    public static String getActivityName(List<FillContext> contexts) {
+        if (contexts == null) return "N/A (null contexts)";
+
+        if (contexts.isEmpty()) return "N/A (empty contexts)";
+
+        final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure();
+        if (structure == null) return "N/A (no AssistStructure)";
+
+        final ComponentName componentName = structure.getActivityComponent();
+        if (componentName == null) return "N/A (no component name)";
+
+        return componentName.flattenToShortString();
+    }
+
+    public static void assertFloat(float actualValue, float expectedValue) {
+        assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
+    }
+
+    public static void assertHasFlags(int actualFlags, int expectedFlags) {
+        assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags)
+                .that(actualFlags & expectedFlags).isEqualTo(expectedFlags);
+    }
+
+    public static String callbackEventAsString(int event) {
+        switch (event) {
+            case AutofillCallback.EVENT_INPUT_HIDDEN:
+                return "HIDDEN";
+            case AutofillCallback.EVENT_INPUT_SHOWN:
+                return "SHOWN";
+            case AutofillCallback.EVENT_INPUT_UNAVAILABLE:
+                return "UNAVAILABLE";
+            default:
+                return "UNKNOWN:" + event;
+        }
+    }
+
+    public static String importantForAutofillAsString(int mode) {
+        switch (mode) {
+            case View.IMPORTANT_FOR_AUTOFILL_AUTO:
+                return "IMPORTANT_FOR_AUTOFILL_AUTO";
+            case View.IMPORTANT_FOR_AUTOFILL_YES:
+                return "IMPORTANT_FOR_AUTOFILL_YES";
+            case View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS:
+                return "IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS";
+            case View.IMPORTANT_FOR_AUTOFILL_NO:
+                return "IMPORTANT_FOR_AUTOFILL_NO";
+            case View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS:
+                return "IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS";
+            default:
+                return "UNKNOWN:" + mode;
+        }
+    }
+
+    public static boolean hasHint(@Nullable String[] hints, @Nullable Object expectedHint) {
+        if (hints == null || expectedHint == null) return false;
+        for (String actualHint : hints) {
+            if (expectedHint.equals(actualHint)) return true;
+        }
+        return false;
+    }
+
+    public static Bundle newClientState(String key, String value) {
+        final Bundle clientState = new Bundle();
+        clientState.putString(key, value);
+        return clientState;
+    }
+
+    public static void assertAuthenticationClientState(String where, Bundle data,
+            String expectedKey, String expectedValue) {
+        assertWithMessage("no client state on %s", where).that(data).isNotNull();
+        final String extraValue = data.getString(expectedKey);
+        assertWithMessage("invalid value for %s on %s", expectedKey, where)
+                .that(extraValue).isEqualTo(expectedValue);
+    }
+
+    /**
+     * Asserts that 2 bitmaps have are the same. If they aren't throws an exception and dump them
+     * locally so their can be visually inspected.
+     *
+     * @param filename base name of the files generated in case of error
+     * @param bitmap1 first bitmap to be compared
+     * @param bitmap2 second bitmap to be compared
+     */
+    // TODO: move to common code
+    public static void assertBitmapsAreSame(@NonNull String filename, @Nullable Bitmap bitmap1,
+            @Nullable Bitmap bitmap2) throws IOException {
+        assertWithMessage("1st bitmap is null").that(bitmap1).isNotNull();
+        assertWithMessage("2nd bitmap is null").that(bitmap2).isNotNull();
+        final boolean same = bitmap1.sameAs(bitmap2);
+        if (same) {
+            Log.v(TAG, "bitmap comparison passed for " + filename);
+            return;
+        }
+
+        final File dir = getLocalDirectory();
+        if (dir == null) {
+            throw new AssertionError("bitmap comparison failed for " + filename
+                    + ", and bitmaps could not be dumped on " + dir);
+        }
+        final File dump1 = dumpBitmap(bitmap1, dir, filename + "-1.png");
+        final File dump2 = dumpBitmap(bitmap2, dir, filename + "-2.png");
+        throw new AssertionError(
+                "bitmap comparison failed; check contents of " + dump1 + " and " + dump2);
+    }
+
+    @Nullable
+    private static File getLocalDirectory() {
+        final File dir = new File(LOCAL_DIRECTORY);
+        dir.mkdirs();
+        if (!dir.exists()) {
+            Log.e(TAG, "Could not create directory " + dir);
+            return null;
+        }
+        return dir;
+    }
+
+    @Nullable
+    private static File createFile(@NonNull File dir, @NonNull String filename) throws IOException {
+        final File file = new File(dir, filename);
+        if (file.exists()) {
+            Log.v(TAG, "Deleting file " + file);
+            file.delete();
+        }
+        if (!file.createNewFile()) {
+            Log.e(TAG, "Could not create file " + file);
+            return null;
+        }
+        return file;
+    }
+
+    @Nullable
+    private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir,
+            @NonNull String filename) throws IOException {
+        final File file = createFile(dir, filename);
+        if (file != null) {
+            dumpBitmap(bitmap, file);
+
+        }
+        return file;
+    }
+
+    @Nullable
+    public static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File file) {
+        Log.i(TAG, "Dumping bitmap at " + file);
+        BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName());
+        return file;
+    }
+
+    /**
+     * Creates a file in the device, using the name of the current test as a prefix.
+     */
+    @Nullable
+    public static File createTestFile(@NonNull String name) throws IOException {
+        final File dir = getLocalDirectory();
+        if (dir == null) return null;
+
+        final String prefix = TestNameUtils.getCurrentTestName().replaceAll("\\.|\\(|\\/", "_")
+                .replaceAll("\\)", "");
+        final String filename = prefix + "-" + name;
+
+        return createFile(dir, filename);
+    }
+
+    /**
+     * Offers an object to a queue or times out.
+     *
+     * @return {@code true} if the offer was accepted, {$code false} if it timed out or was
+     * interrupted.
+     */
+    public static <T> boolean offer(BlockingQueue<T> queue, T obj, long timeoutMs) {
+        boolean offered = false;
+        try {
+            offered = queue.offer(obj, timeoutMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.w(TAG, "interrupted offering", e);
+            Thread.currentThread().interrupt();
+        }
+        if (!offered) {
+            Log.e(TAG, "could not offer " + obj + " in " + timeoutMs + "ms");
+        }
+        return offered;
+    }
+
+    /**
+     * Calls this method to assert given {@code string} is equal to {@link #LARGE_STRING}, as
+     * comparing its value using standard assertions might ANR.
+     */
+    public static void assertEqualsToLargeString(@NonNull String string) {
+        assertThat(string).isNotNull();
+        assertThat(string).hasLength(LARGE_STRING_SIZE);
+        assertThat(string.charAt(0)).isEqualTo(LARGE_STRING_CHAR);
+        assertThat(string.charAt(LARGE_STRING_SIZE - 1)).isEqualTo(LARGE_STRING_CHAR);
+    }
+
+    /**
+     * Asserts that autofill is enabled in the context, retrying if necessariy.
+     */
+    public static void assertAutofillEnabled(@NonNull Context context, boolean expected)
+            throws Exception {
+        assertAutofillEnabled(context.getSystemService(AutofillManager.class), expected);
+    }
+
+    /**
+     * Asserts that autofill is enabled in the manager, retrying if necessariy.
+     */
+    public static void assertAutofillEnabled(@NonNull AutofillManager afm, boolean expected)
+            throws Exception {
+        Timeouts.IDLE_UNBIND_TIMEOUT.run("assertEnabled(" + expected + ")", () -> {
+            final boolean actual = afm.isEnabled();
+            Log.v(TAG, "assertEnabled(): expected=" + expected + ", actual=" + actual);
+            return actual == expected ? "not_used" : null;
+        });
+    }
+
+    /**
+     * Asserts these autofill ids are the same, except for the session.
+     */
+    public static void assertEqualsIgnoreSession(@NonNull AutofillId id1, @NonNull AutofillId id2) {
+        assertWithMessage("id1 is null").that(id1).isNotNull();
+        assertWithMessage("id2 is null").that(id2).isNotNull();
+        assertWithMessage("%s is not equal to %s", id1, id2).that(id1.equalsIgnoreSession(id2))
+                .isTrue();
+    }
+
+    /**
+     * Asserts {@link View#isAutofilled()} state of the given view, waiting if necessarity to avoid
+     * race conditions.
+     */
+    public static void assertViewAutofillState(@NonNull View view, boolean expected)
+            throws Exception {
+        Timeouts.FILL_TIMEOUT.run("assertViewAutofillState(" + view + ", " + expected + ")",
+                () -> {
+                    final boolean actual = view.isAutofilled();
+                    Log.v(TAG, "assertViewAutofillState(): expected=" + expected + ", actual="
+                            + actual);
+                    return actual == expected ? "not_used" : null;
+                });
+    }
+
+    /**
+     * Allows the test to draw overlaid windows.
+     *
+     * <p>Should call {@link #disallowOverlays()} afterwards.
+     */
+    public static void allowOverlays() {
+        ShellUtils.setOverlayPermissions(MY_PACKAGE, true);
+    }
+
+    /**
+     * Disallow the test to draw overlaid windows.
+     *
+     * <p>Should call {@link #disallowOverlays()} afterwards.
+     */
+    public static void disallowOverlays() {
+        ShellUtils.setOverlayPermissions(MY_PACKAGE, false);
+    }
+
+    public static RemoteViews createPresentation(String message) {
+        final RemoteViews presentation = new RemoteViews(getContext()
+                .getPackageName(), R.layout.list_item);
+        presentation.setTextViewText(R.id.text1, message);
+        return presentation;
+    }
+
+    public static InlinePresentation createInlinePresentation(String message) {
+        final PendingIntent dummyIntent =
+                PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
+        return createInlinePresentation(message, dummyIntent, false);
+    }
+
+    public static InlinePresentation createInlinePresentation(String message,
+            PendingIntent attribution) {
+        return createInlinePresentation(message, attribution, false);
+    }
+
+    public static InlinePresentation createPinnedInlinePresentation(String message) {
+        final PendingIntent dummyIntent =
+                PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
+        return createInlinePresentation(message, dummyIntent, true);
+    }
+
+    private static InlinePresentation createInlinePresentation(@NonNull String message,
+            @NonNull PendingIntent attribution, boolean pinned) {
+        return new InlinePresentation(
+                InlineSuggestionUi.newContentBuilder(attribution)
+                        .setTitle(message).build().getSlice(),
+                new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
+                        .build(), /* pinned= */ pinned);
+    }
+
+    public static void mockSwitchInputMethod(@NonNull Context context) throws Exception {
+        final ContentResolver cr = context.getContentResolver();
+        final int subtype = Settings.Secure.getInt(cr, SELECTED_INPUT_METHOD_SUBTYPE);
+        Settings.Secure.putInt(cr, SELECTED_INPUT_METHOD_SUBTYPE, subtype);
+    }
+
+    /**
+     * Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist.
+     */
+    public static void resetApplicationAutofillOptions(@NonNull Context context) {
+        AutofillOptions options = AutofillOptions.forWhitelistingItself();
+        options.augmentedAutofillEnabled = false;
+        context.getApplicationContext().setAutofillOptions(options);
+    }
+
+    private Helper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+
+    public static class FieldClassificationResult {
+        public final AutofillId id;
+        public final String[] categoryIds;
+        public final float[] scores;
+
+        public FieldClassificationResult(@NonNull AutofillId id, @NonNull String categoryId,
+                float score) {
+            this(id, new String[]{categoryId}, new float[]{score});
+        }
+
+        public FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] categoryIds,
+                float[] scores) {
+            this.id = id;
+            this.categoryIds = categoryIds;
+            this.scores = scores;
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/IdMode.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/IdMode.java
new file mode 100644
index 0000000..6d54d0c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/IdMode.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+/**
+ * Enum used to explain the meaning of node ids used by test cases.
+ */
+public enum IdMode {
+    RESOURCE_ID,
+    HTML_NAME,
+    HTML_NAME_OR_RESOURCE_ID
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
new file mode 100644
index 0000000..88f97c2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.DATASET_PICKER_NOT_SHOWN_NAPTIME_MS;
+import static android.autofillservice.cts.testcore.Timeouts.LONG_PRESS_MS;
+import static android.autofillservice.cts.testcore.Timeouts.UI_TIMEOUT;
+
+import android.content.pm.PackageManager;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+
+import com.android.compatibility.common.util.RequiredFeatureRule;
+import com.android.compatibility.common.util.Timeout;
+import com.android.cts.mockime.MockIme;
+
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+/**
+ * UiBot for the inline suggestion.
+ */
+public final class InlineUiBot extends UiBot {
+
+    private static final String TAG = "AutoFillInlineCtsUiBot";
+    public static final String SUGGESTION_STRIP_DESC = "MockIme Inline Suggestion View";
+
+    private static final BySelector SUGGESTION_STRIP_SELECTOR = By.desc(SUGGESTION_STRIP_DESC);
+
+    private static final RequiredFeatureRule REQUIRES_IME_RULE = new RequiredFeatureRule(
+            PackageManager.FEATURE_INPUT_METHODS);
+
+    public InlineUiBot() {
+        this(UI_TIMEOUT);
+    }
+
+    public InlineUiBot(Timeout defaultTimeout) {
+        super(defaultTimeout);
+    }
+
+    public static RuleChain annotateRule(TestRule rule) {
+        return RuleChain.outerRule(REQUIRES_IME_RULE).around(rule);
+    }
+
+    @Override
+    public void assertNoDatasets() throws Exception {
+        assertNoDatasetsEver();
+    }
+
+    @Override
+    public void assertNoDatasetsEver() throws Exception {
+        assertNeverShown("suggestion strip", SUGGESTION_STRIP_SELECTOR,
+                DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
+    }
+
+    /**
+     * Selects the suggestion in the {@link MockIme}'s suggestion strip by the given text.
+     */
+    public void selectSuggestion(String name) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        final UiObject2 dataset = strip.findObject(By.text(name));
+        if (dataset == null) {
+            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
+        }
+        dataset.click();
+    }
+
+    @Override
+    public void selectDataset(String name) throws Exception {
+        selectSuggestion(name);
+    }
+
+    @Override
+    public void longPressSuggestion(String name) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        final UiObject2 dataset = strip.findObject(By.text(name));
+        if (dataset == null) {
+            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
+        }
+        dataset.click(LONG_PRESS_MS);
+    }
+
+    @Override
+    public UiObject2 assertDatasets(String...names) throws Exception {
+        final UiObject2 picker = findSuggestionStrip(UI_TIMEOUT);
+        return assertDatasets(picker, names);
+    }
+
+    @Override
+    public void assertSuggestion(String name) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        final UiObject2 dataset = strip.findObject(By.text(name));
+        if (dataset == null) {
+            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
+        }
+    }
+
+    @Override
+    public void assertNoSuggestion(String name) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        final UiObject2 dataset = strip.findObject(By.text(name));
+        if (dataset != null) {
+            throw new AssertionError("has dataset " + name + " in " + getChildrenAsText(strip));
+        }
+    }
+
+    @Override
+    public void scrollSuggestionView(Direction direction, int speed) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        strip.fling(direction, speed);
+    }
+
+    private UiObject2 findSuggestionStrip(Timeout timeout) throws Exception {
+        return waitForObject(SUGGESTION_STRIP_SELECTOR, timeout);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillService.java
new file mode 100644
index 0000000..0903236
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillService.java
@@ -0,0 +1,737 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.FAILURE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.NULL;
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.TIMEOUT;
+import static android.autofillservice.cts.testcore.Helper.dumpStructure;
+import static android.autofillservice.cts.testcore.Helper.getActivityName;
+import static android.autofillservice.cts.testcore.Timeouts.CONNECTION_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.FILL_EVENTS_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.IDLE_UNBIND_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.RESPONSE_DELAY_MS;
+import static android.autofillservice.cts.testcore.Timeouts.SAVE_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.CannedFillResponse.ResponseType;
+import android.content.ComponentName;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveCallback;
+import android.util.Log;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import androidx.annotation.Nullable;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.TestNameUtils;
+import com.android.compatibility.common.util.Timeout;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Implementation of {@link AutofillService} used in the tests.
+ */
+public class InstrumentedAutoFillService extends AutofillService {
+
+    public static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
+    public static final String SERVICE_CLASS = "InstrumentedAutoFillService";
+
+    public static final String SERVICE_NAME = SERVICE_PACKAGE + "/.testcore." + SERVICE_CLASS;
+
+    public static String sServiceLabel = SERVICE_CLASS;
+
+    // TODO(b/125844305): remove once fixed
+    private static final boolean FAIL_ON_INVALID_CONNECTION_STATE = false;
+
+    private static final String TAG = "InstrumentedAutoFillService";
+
+    private static final boolean DUMP_FILL_REQUESTS = false;
+    private static final boolean DUMP_SAVE_REQUESTS = false;
+
+    protected static final AtomicReference<InstrumentedAutoFillService> sInstance =
+            new AtomicReference<>();
+    private static final Replier sReplier = new Replier();
+
+    private static AtomicBoolean sConnected = new AtomicBoolean(false);
+
+    // We must handle all requests in a separate thread as the service's main thread is the also
+    // the UI thread of the test process and we don't want to hose it in case of failures here
+    private static final HandlerThread sMyThread = new HandlerThread("MyServiceThread");
+    private final Handler mHandler;
+
+    private boolean mConnected;
+
+    static {
+        Log.i(TAG, "Starting thread " + sMyThread);
+        sMyThread.start();
+    }
+
+    public InstrumentedAutoFillService() {
+        sInstance.set(this);
+        sServiceLabel = SERVICE_CLASS;
+        mHandler = Handler.createAsync(sMyThread.getLooper());
+        sReplier.setHandler(mHandler);
+    }
+
+    private static InstrumentedAutoFillService peekInstance() {
+        return sInstance.get();
+    }
+
+    /**
+     * Gets the list of fill events in the {@link FillEventHistory}, waiting until it has the
+     * expected size.
+     */
+    public static List<Event> getFillEvents(int expectedSize) throws Exception {
+        final List<Event> events = getFillEventHistory(expectedSize).getEvents();
+        // Validation check
+        if (expectedSize > 0 && events == null || events.size() != expectedSize) {
+            throw new IllegalStateException("INTERNAL ERROR: events should have " + expectedSize
+                    + ", but it is: " + events);
+        }
+        return events;
+    }
+
+    /**
+     * Gets the {@link FillEventHistory}, waiting until it has the expected size.
+     */
+    public static FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
+        final InstrumentedAutoFillService service = peekInstance();
+
+        if (expectedSize == 0) {
+            // Need to always sleep as there is no condition / callback to be used to wait until
+            // expected number of events is set.
+            SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
+            final FillEventHistory history = service.getFillEventHistory();
+            assertThat(history.getEvents()).isNull();
+            return history;
+        }
+
+        return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
+            final FillEventHistory history = service.getFillEventHistory();
+            if (history == null) {
+                return null;
+            }
+            final List<Event> events = history.getEvents();
+            if (events != null) {
+                if (events.size() != expectedSize) {
+                    Log.v(TAG, "Didn't get " + expectedSize + " events yet: " + events);
+                    return null;
+                }
+            } else {
+                Log.v(TAG, "Events is still null (expecting " + expectedSize + ")");
+                return null;
+            }
+            return history;
+        });
+    }
+
+    /**
+     * Asserts there is no {@link FillEventHistory}.
+     */
+    public static void assertNoFillEventHistory() {
+        // Need to always sleep as there is no condition / callback to be used to wait until
+        // expected number of events is set.
+        SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
+        assertThat(peekInstance().getFillEventHistory()).isNull();
+
+    }
+
+    /**
+     * Gets the service label associated with the current instance.
+     */
+    public static String getServiceLabel() {
+        return sServiceLabel;
+    }
+
+    private void handleConnected(boolean connected) {
+        Log.v(TAG, "handleConnected(): from " + sConnected.get() + " to " + connected);
+        sConnected.set(connected);
+    }
+
+    @Override
+    public void onConnected() {
+        Log.v(TAG, "onConnected");
+        if (mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
+            dumpSelf();
+            sReplier.addException(new IllegalStateException("onConnected() called again"));
+        }
+        mConnected = true;
+        mHandler.post(() -> handleConnected(true));
+    }
+
+    @Override
+    public void onDisconnected() {
+        Log.v(TAG, "onDisconnected");
+        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
+            dumpSelf();
+            sReplier.addException(
+                    new IllegalStateException("onDisconnected() called when disconnected"));
+        }
+        mConnected = false;
+        mHandler.post(() -> handleConnected(false));
+    }
+
+    @Override
+    public void onFillRequest(android.service.autofill.FillRequest request,
+            CancellationSignal cancellationSignal, FillCallback callback) {
+        final ComponentName component = getLastActivityComponent(request.getFillContexts());
+        if (DUMP_FILL_REQUESTS) {
+            dumpStructure("onFillRequest()", request.getFillContexts());
+        } else {
+            Log.i(TAG, "onFillRequest() for " + component.toShortString());
+        }
+        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
+            dumpSelf();
+            sReplier.addException(
+                    new IllegalStateException("onFillRequest() called when disconnected"));
+        }
+
+        if (!TestNameUtils.isRunningTest()) {
+            Log.e(TAG, "onFillRequest(" + component + ") called after tests finished");
+            return;
+        }
+        if (!fromSamePackage(component))  {
+            Log.w(TAG, "Ignoring onFillRequest() from different package: " + component);
+            return;
+        }
+        mHandler.post(
+                () -> sReplier.onFillRequest(request.getFillContexts(), request.getClientState(),
+                        cancellationSignal, callback, request.getFlags(),
+                        request.getInlineSuggestionsRequest(), request.getId()));
+    }
+
+    @Override
+    public void onSaveRequest(android.service.autofill.SaveRequest request,
+            SaveCallback callback) {
+        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
+            dumpSelf();
+            sReplier.addException(
+                    new IllegalStateException("onSaveRequest() called when disconnected"));
+        }
+        mHandler.post(()->handleSaveRequest(request, callback));
+    }
+
+    private void handleSaveRequest(android.service.autofill.SaveRequest request,
+            SaveCallback callback) {
+        final ComponentName component = getLastActivityComponent(request.getFillContexts());
+        if (!TestNameUtils.isRunningTest()) {
+            Log.e(TAG, "onSaveRequest(" + component + ") called after tests finished");
+            return;
+        }
+        if (!fromSamePackage(component)) {
+            Log.w(TAG, "Ignoring onSaveRequest() from different package: " + component);
+            return;
+        }
+        if (DUMP_SAVE_REQUESTS) {
+            dumpStructure("onSaveRequest()", request.getFillContexts());
+        } else {
+            Log.i(TAG, "onSaveRequest() for " + component.toShortString());
+        }
+        mHandler.post(() -> sReplier.onSaveRequest(request.getFillContexts(),
+                request.getClientState(), callback,
+                request.getDatasetIds()));
+    }
+
+    public static boolean isConnected() {
+        return sConnected.get();
+    }
+
+    private boolean fromSamePackage(ComponentName component) {
+        final String actualPackage = component.getPackageName();
+        if (!actualPackage.equals(getPackageName())
+                && !actualPackage.equals(sReplier.mAcceptedPackageName)) {
+            Log.w(TAG, "Got request from package " + actualPackage);
+            return false;
+        }
+        return true;
+    }
+
+    private ComponentName getLastActivityComponent(List<FillContext> contexts) {
+        return contexts.get(contexts.size() - 1).getStructure().getActivityComponent();
+    }
+
+    private void dumpSelf()  {
+        try {
+            try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
+                dump(null, pw, null);
+                pw.flush();
+                final String dump = sw.toString();
+                Log.e(TAG, "dumpSelf(): " + dump);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "I don't always fail to dump, but when I do, I dump the failure", e);
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("sConnected: "); pw.println(sConnected);
+        pw.print("mConnected: "); pw.println(mConnected);
+        pw.print("sInstance: "); pw.println(sInstance);
+        pw.println("sReplier: "); sReplier.dump(pw);
+    }
+
+    /**
+     * Waits until {@link #onConnected()} is called, or fails if it times out.
+     *
+     * <p>This method is useful on tests that explicitly verifies the connection, but should be
+     * avoided in other tests, as it adds extra time to the test execution (and flakiness in cases
+     * where the service might have being disconnected already; for example, if the fill request
+     * was replied with a {@code null} response) - if a text needs to block until the service
+     * receives a callback, it should use {@link Replier#getNextFillRequest()} instead.
+     */
+    public static void waitUntilConnected() throws Exception {
+        waitConnectionState(CONNECTION_TIMEOUT, true);
+    }
+
+    /**
+     * Waits until {@link #onDisconnected()} is called, or fails if it times out.
+     *
+     * <p>This method is useful on tests that explicitly verifies the connection, but should be
+     * avoided in other tests, as it adds extra time to the test execution.
+     */
+    public static void waitUntilDisconnected() throws Exception {
+        waitConnectionState(IDLE_UNBIND_TIMEOUT, false);
+    }
+
+    private static void waitConnectionState(Timeout timeout, boolean expected) throws Exception {
+        timeout.run("wait for connected=" + expected,  () -> {
+            return isConnected() == expected ? Boolean.TRUE : null;
+        });
+    }
+
+    /**
+     * Gets the {@link Replier} singleton.
+     */
+    public static Replier getReplier() {
+        return sReplier;
+    }
+
+    public static void resetStaticState() {
+        sInstance.set(null);
+        sConnected.set(false);
+        sServiceLabel = SERVICE_CLASS;
+    }
+
+    /**
+     * POJO representation of the contents of a
+     * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
+     * CancellationSignal, FillCallback)} that can be asserted at the end of a test case.
+     */
+    public static final class FillRequest {
+        public final AssistStructure structure;
+        public final List<FillContext> contexts;
+        public final Bundle data;
+        public final CancellationSignal cancellationSignal;
+        public final FillCallback callback;
+        public final int flags;
+        public final InlineSuggestionsRequest inlineRequest;
+
+        private FillRequest(List<FillContext> contexts, Bundle data,
+                CancellationSignal cancellationSignal, FillCallback callback, int flags,
+                InlineSuggestionsRequest inlineRequest) {
+            this.contexts = contexts;
+            this.data = data;
+            this.cancellationSignal = cancellationSignal;
+            this.callback = callback;
+            this.flags = flags;
+            this.structure = contexts.get(contexts.size() - 1).getStructure();
+            this.inlineRequest = inlineRequest;
+        }
+
+        @Override
+        public String toString() {
+            return "FillRequest[activity=" + getActivityName(contexts) + ", flags=" + flags
+                    + ", bundle=" + data + ", structure=" + Helper.toString(structure) + "]";
+        }
+    }
+
+    /**
+     * POJO representation of the contents of a
+     * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)}
+     * that can be asserted at the end of a test case.
+     */
+    public static final class SaveRequest {
+        public final List<FillContext> contexts;
+        public final AssistStructure structure;
+        public final Bundle data;
+        public final SaveCallback callback;
+        public final List<String> datasetIds;
+
+        private SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
+                List<String> datasetIds) {
+            if (contexts != null && contexts.size() > 0) {
+                structure = contexts.get(contexts.size() - 1).getStructure();
+            } else {
+                structure = null;
+            }
+            this.contexts = contexts;
+            this.data = data;
+            this.callback = callback;
+            this.datasetIds = datasetIds;
+        }
+
+        @Override
+        public String toString() {
+            return "SaveRequest:" + getActivityName(contexts);
+        }
+    }
+
+    /**
+     * Object used to answer a
+     * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
+     * CancellationSignal, FillCallback)}
+     * on behalf of a unit test method.
+     */
+    public static final class Replier {
+
+        private final BlockingQueue<CannedFillResponse> mResponses = new LinkedBlockingQueue<>();
+        private final BlockingQueue<FillRequest> mFillRequests = new LinkedBlockingQueue<>();
+        private final BlockingQueue<SaveRequest> mSaveRequests = new LinkedBlockingQueue<>();
+
+        private List<Throwable> mExceptions;
+        private IntentSender mOnSaveIntentSender;
+        private String mAcceptedPackageName;
+
+        private Handler mHandler;
+
+        private boolean mReportUnhandledFillRequest = true;
+        private boolean mReportUnhandledSaveRequest = true;
+
+        private Replier() {
+        }
+
+        private IdMode mIdMode = IdMode.RESOURCE_ID;
+
+        public void setIdMode(IdMode mode) {
+            this.mIdMode = mode;
+        }
+
+        public void acceptRequestsFromPackage(String packageName) {
+            mAcceptedPackageName = packageName;
+        }
+
+        /**
+         * Gets the exceptions thrown asynchronously, if any.
+         */
+        @Nullable
+        public List<Throwable> getExceptions() {
+            return mExceptions;
+        }
+
+        private void addException(@Nullable Throwable e) {
+            if (e == null) return;
+
+            if (mExceptions == null) {
+                mExceptions = new ArrayList<>();
+            }
+            mExceptions.add(e);
+        }
+
+        /**
+         * Sets the expectation for the next {@code onFillRequest} as {@link FillResponse} with just
+         * one {@link Dataset}.
+         */
+        public Replier addResponse(CannedDataset dataset) {
+            return addResponse(new CannedFillResponse.Builder()
+                    .addDataset(dataset)
+                    .build());
+        }
+
+        /**
+         * Sets the expectation for the next {@code onFillRequest}.
+         */
+        public Replier addResponse(CannedFillResponse response) {
+            if (response == null) {
+                throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
+            }
+            mResponses.add(response);
+            return this;
+        }
+
+        /**
+         * Sets the {@link IntentSender} that is passed to
+         * {@link SaveCallback#onSuccess(IntentSender)}.
+         */
+        public Replier setOnSave(IntentSender intentSender) {
+            mOnSaveIntentSender = intentSender;
+            return this;
+        }
+
+        /**
+         * Gets the next fill request, in the order received.
+         */
+        public FillRequest getNextFillRequest() {
+            FillRequest request;
+            try {
+                request = mFillRequests.poll(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Interrupted", e);
+            }
+            if (request == null) {
+                throw new RetryableException(FILL_TIMEOUT, "onFillRequest() not called");
+            }
+            return request;
+        }
+
+        /**
+         * Asserts that {@link #onFillRequest(List, Bundle, CancellationSignal, FillCallback, int)}
+         * was not called.
+         *
+         * <p>Should only be called in cases where it's not expected to be called, as it will
+         * sleep for a few ms.
+         */
+        public void assertOnFillRequestNotCalled() {
+            SystemClock.sleep(FILL_TIMEOUT.getMaxValue());
+            assertThat(mFillRequests).isEmpty();
+        }
+
+        /**
+         * Asserts all {@link AutofillService#onFillRequest(
+         * android.service.autofill.FillRequest,  CancellationSignal, FillCallback) fill requests}
+         * received by the service were properly {@link #getNextFillRequest() handled} by the test
+         * case.
+         */
+        public void assertNoUnhandledFillRequests() {
+            if (mFillRequests.isEmpty()) return; // Good job, test case!
+
+            if (!mReportUnhandledFillRequest) {
+                // Just log, so it's not thrown again on @After if already thrown on main body
+                Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
+                        + "but logging just in case: " + mFillRequests);
+                return;
+            }
+
+            mReportUnhandledFillRequest = false;
+            throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
+                    + mFillRequests);
+        }
+
+        /**
+         * Gets the current number of unhandled requests.
+         */
+        public int getNumberUnhandledFillRequests() {
+            return mFillRequests.size();
+        }
+
+        /**
+         * Gets the next save request, in the order received.
+         *
+         * <p>Typically called at the end of a test case, to assert the initial request.
+         */
+        public SaveRequest getNextSaveRequest() {
+            SaveRequest request;
+            try {
+                request = mSaveRequests.poll(SAVE_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Interrupted", e);
+            }
+            if (request == null) {
+                throw new RetryableException(SAVE_TIMEOUT, "onSaveRequest() not called");
+            }
+            return request;
+        }
+
+        /**
+         * Asserts all
+         * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)
+         * save requests} received by the service were properly
+         * {@link #getNextFillRequest() handled} by the test case.
+         */
+        public void assertNoUnhandledSaveRequests() {
+            if (mSaveRequests.isEmpty()) return; // Good job, test case!
+
+            if (!mReportUnhandledSaveRequest) {
+                // Just log, so it's not thrown again on @After if already thrown on main body
+                Log.d(TAG, "assertNoUnhandledSaveRequests(): already reported, "
+                        + "but logging just in case: " + mSaveRequests);
+                return;
+            }
+
+            mReportUnhandledSaveRequest = false;
+            throw new AssertionError(mSaveRequests.size() + " unhandled save requests: "
+                    + mSaveRequests);
+        }
+
+        public void setHandler(Handler handler) {
+            mHandler = handler;
+        }
+
+        /**
+         * Resets its internal state.
+         */
+        public void reset() {
+            mResponses.clear();
+            mFillRequests.clear();
+            mSaveRequests.clear();
+            mExceptions = null;
+            mOnSaveIntentSender = null;
+            mAcceptedPackageName = null;
+            mReportUnhandledFillRequest = true;
+            mReportUnhandledSaveRequest = true;
+        }
+
+        private void onFillRequest(List<FillContext> contexts, Bundle data,
+                CancellationSignal cancellationSignal, FillCallback callback, int flags,
+                InlineSuggestionsRequest inlineRequest, int requestId) {
+            try {
+                CannedFillResponse response = null;
+                try {
+                    response = mResponses.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "Interrupted getting CannedResponse: " + e);
+                    Thread.currentThread().interrupt();
+                    addException(e);
+                    return;
+                }
+                if (response == null) {
+                    final String activityName = getActivityName(contexts);
+                    final String msg = "onFillRequest() for activity " + activityName
+                            + " received when no canned response was set.";
+                    dumpStructure(msg, contexts);
+                    return;
+                }
+                if (response.getResponseType() == NULL) {
+                    Log.d(TAG, "onFillRequest(): replying with null");
+                    callback.onSuccess(null);
+                    return;
+                }
+
+                if (response.getResponseType() == TIMEOUT) {
+                    Log.d(TAG, "onFillRequest(): not replying at all");
+                    return;
+                }
+
+                if (response.getResponseType() == FAILURE) {
+                    Log.d(TAG, "onFillRequest(): replying with failure");
+                    callback.onFailure("D'OH!");
+                    return;
+                }
+
+                if (response.getResponseType() == ResponseType.NO_MORE) {
+                    Log.w(TAG, "onFillRequest(): replying with null when not expecting more");
+                    addException(new IllegalStateException("got unexpected request"));
+                    callback.onSuccess(null);
+                    return;
+                }
+
+                final String failureMessage = response.getFailureMessage();
+                if (failureMessage != null) {
+                    Log.v(TAG, "onFillRequest(): failureMessage = " + failureMessage);
+                    callback.onFailure(failureMessage);
+                    return;
+                }
+
+                final FillResponse fillResponse;
+
+                switch (mIdMode) {
+                    case RESOURCE_ID:
+                        fillResponse = response.asFillResponse(contexts,
+                                (id) -> Helper.findNodeByResourceId(contexts, id));
+                        break;
+                    case HTML_NAME:
+                        fillResponse = response.asFillResponse(contexts,
+                                (name) -> Helper.findNodeByHtmlName(contexts, name));
+                        break;
+                    case HTML_NAME_OR_RESOURCE_ID:
+                        fillResponse = response.asFillResponse(contexts,
+                                (id) -> Helper.findNodeByHtmlNameOrResourceId(contexts, id));
+                        break;
+                    default:
+                        throw new IllegalStateException("Unknown id mode: " + mIdMode);
+                }
+
+                if (response.getResponseType() == ResponseType.DELAY) {
+                    mHandler.postDelayed(() -> {
+                        Log.v(TAG,
+                                "onFillRequest(" + requestId + "): fillResponse = " + fillResponse);
+                        callback.onSuccess(fillResponse);
+                        // Add a fill request to let test case know response was sent.
+                        Helper.offer(mFillRequests,
+                                new FillRequest(contexts, data, cancellationSignal, callback,
+                                        flags, inlineRequest), CONNECTION_TIMEOUT.ms());
+                    }, RESPONSE_DELAY_MS);
+                } else {
+                    Log.v(TAG, "onFillRequest(" + requestId + "): fillResponse = " + fillResponse);
+                    callback.onSuccess(fillResponse);
+                }
+            } catch (Throwable t) {
+                addException(t);
+            } finally {
+                Helper.offer(mFillRequests, new FillRequest(contexts, data, cancellationSignal,
+                        callback, flags, inlineRequest), CONNECTION_TIMEOUT.ms());
+            }
+        }
+
+        private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
+                List<String> datasetIds) {
+            Log.d(TAG, "onSaveRequest(): sender=" + mOnSaveIntentSender);
+
+            try {
+                if (mOnSaveIntentSender != null) {
+                    callback.onSuccess(mOnSaveIntentSender);
+                } else {
+                    callback.onSuccess();
+                }
+            } finally {
+                Helper.offer(mSaveRequests, new SaveRequest(contexts, data, callback, datasetIds),
+                        CONNECTION_TIMEOUT.ms());
+            }
+        }
+
+        private void dump(PrintWriter pw) {
+            pw.print("mResponses: "); pw.println(mResponses);
+            pw.print("mFillRequests: "); pw.println(mFillRequests);
+            pw.print("mSaveRequests: "); pw.println(mSaveRequests);
+            pw.print("mExceptions: "); pw.println(mExceptions);
+            pw.print("mOnSaveIntentSender: "); pw.println(mOnSaveIntentSender);
+            pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
+            pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
+            pw.print("mReportUnhandledFillRequest: "); pw.println(mReportUnhandledSaveRequest);
+            pw.print("mIdMode: "); pw.println(mIdMode);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceCompatMode.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceCompatMode.java
new file mode 100644
index 0000000..fc35412
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceCompatMode.java
@@ -0,0 +1,37 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import android.service.autofill.AutofillService;
+
+/**
+ * Implementation of {@link AutofillService} using A11Y compat mode used in the tests.
+ */
+public class InstrumentedAutoFillServiceCompatMode extends InstrumentedAutoFillService {
+
+    @SuppressWarnings("hiding")
+    public static final String SERVICE_PACKAGE = "android.autofillservice.cts";
+    @SuppressWarnings("hiding")
+    public static final String SERVICE_CLASS = "testcore.InstrumentedAutoFillServiceCompatMode";
+    @SuppressWarnings("hiding")
+    public static final String SERVICE_NAME = SERVICE_PACKAGE + "/." + SERVICE_CLASS;
+
+    public InstrumentedAutoFillServiceCompatMode() {
+        sInstance.set(this);
+        sServiceLabel = SERVICE_CLASS;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceInlineEnabled.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceInlineEnabled.java
new file mode 100644
index 0000000..a29c01b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceInlineEnabled.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import android.service.autofill.AutofillService;
+
+/**
+ * Implementation of {@link AutofillService} that has inline suggestions support enabled.
+ */
+public class InstrumentedAutoFillServiceInlineEnabled extends InstrumentedAutoFillService {
+    @SuppressWarnings("hiding")
+    static final String SERVICE_PACKAGE = "android.autofillservice.cts";
+    @SuppressWarnings("hiding")
+    static final String SERVICE_CLASS = "InstrumentedAutoFillServiceInlineEnabled";
+    @SuppressWarnings("hiding")
+    public static final String SERVICE_NAME = SERVICE_PACKAGE + "/.testcore." + SERVICE_CLASS;
+
+    public InstrumentedAutoFillServiceInlineEnabled() {
+        sInstance.set(this);
+        sServiceLabel = SERVICE_CLASS;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MaxVisibleDatasetsRule.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MaxVisibleDatasetsRule.java
new file mode 100644
index 0000000..b1b4327
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MaxVisibleDatasetsRule.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Custom JUnit4 rule that improves autofill-related environment by:
+ *
+ * <ol>
+ *   <li>Setting max_visible_datasets before and after test.
+ * </ol>
+ */
+public final class MaxVisibleDatasetsRule implements TestRule {
+
+    private static final String TAG = MaxVisibleDatasetsRule.class.getSimpleName();
+
+    private final int mMaxNumber;
+
+    /**
+     * Creates a MaxVisibleDatasetsRule with given datasets values.
+     *
+     * @param maxNumber The desired max_visible_datasets value for a test,
+     * after the test it will be replaced by the original value
+     */
+    public MaxVisibleDatasetsRule(int maxNumber) {
+        mMaxNumber = maxNumber;
+    }
+
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                final int original = Helper.getMaxVisibleDatasets();
+                Helper.setMaxVisibleDatasets(mMaxNumber);
+                try {
+                    base.evaluate();
+                } finally {
+                    Helper.setMaxVisibleDatasets(original);
+                }
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesRadioGroupListener.java
new file mode 100644
index 0000000..a8b4317
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesRadioGroupListener.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.widget.RadioGroup;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link android.widget.RadioGroup.OnCheckedChangeListener} used to assert an
+ * {@link RadioGroup} was auto-filled properly.
+ */
+public final class MultipleTimesRadioGroupListener implements RadioGroup.OnCheckedChangeListener {
+    private final String mName;
+    private final CountDownLatch mLatch;
+    private final RadioGroup mRadioGroup;
+    private final int mExpected;
+
+    public MultipleTimesRadioGroupListener(String name, int times, RadioGroup radioGroup,
+            int expectedAutoFilledValue) {
+        mName = name;
+        mRadioGroup = radioGroup;
+        mExpected = expectedAutoFilledValue;
+        mLatch = new CountDownLatch(times);
+    }
+
+    @Override
+    public void onCheckedChanged(RadioGroup group, int checkedId) {
+        mLatch.countDown();
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), mName)
+            .that(set).isTrue();
+        final int actual = mRadioGroup.getAutofillValue().getListValue();
+        assertWithMessage("Wrong auto-fill value on RadioGroup %s", mName)
+            .that(actual).isEqualTo(mExpected);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTextWatcher.java
new file mode 100644
index 0000000..9e59634
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTextWatcher.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.widget.EditText;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link TextWatcher} used to assert a {@link EditText} was set multiple times.
+ */
+public class MultipleTimesTextWatcher implements TextWatcher {
+    private static final String TAG = "MultipleTimesTextWatcher";
+
+    private final String mName;
+    private final CountDownLatch mLatch;
+    private final EditText mEditText;
+    private final CharSequence mExpected;
+
+    public MultipleTimesTextWatcher(String name, int times, EditText editText,
+            CharSequence expectedAutofillValue) {
+        this.mName = name;
+        this.mEditText = editText;
+        this.mExpected = expectedAutofillValue;
+        this.mLatch = new CountDownLatch(times);
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        Log.v(TAG, "onTextChanged(" + mLatch.getCount() + "): " + mName + " = " + s);
+        mLatch.countDown();
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        if (!set) {
+            throw new RetryableException(FILL_TIMEOUT, "Timeout (%s ms) on EditText %s",
+                    FILL_TIMEOUT.ms(), mName);
+        }
+        final String actual = mEditText.getText().toString();
+        assertWithMessage("Wrong auto-fill value on EditText %s", mName)
+                .that(actual).isEqualTo(mExpected.toString());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTimeListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTimeListener.java
new file mode 100644
index 0000000..10d87e1
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTimeListener.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.widget.TimePicker;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@OnDateChangedListener} used to assert a {@link TimePicker} was auto-filled properly.
+ */
+public final class MultipleTimesTimeListener implements TimePicker.OnTimeChangedListener {
+    private final String name;
+    private final CountDownLatch latch;
+    private final TimePicker timePicker;
+    private final int expectedHour;
+    private final int expectedMinute;
+
+    public MultipleTimesTimeListener(String name, int times, TimePicker timePicker,
+            int expectedHour, int expectedMinute) {
+        this.name = name;
+        this.timePicker = timePicker;
+        this.expectedHour = expectedHour;
+        this.expectedMinute = expectedMinute;
+        this.latch = new CountDownLatch(times);
+    }
+
+    @Override
+    public void onTimeChanged(TimePicker view, int hour, int minute) {
+        latch.countDown();
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on TimePicker %s", FILL_TIMEOUT.ms(), name)
+                .that(set).isTrue();
+        assertWithMessage("Wrong hour on TimePicker %s", name)
+                .that(timePicker.getHour()).isEqualTo(expectedHour);
+        assertWithMessage("Wrong minute on TimePicker %s", name)
+                .that(timePicker.getMinute()).isEqualTo(expectedMinute);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillCallback.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillCallback.java
new file mode 100644
index 0000000..7c43aab
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillCallback.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Helper.callbackEventAsString;
+import static android.autofillservice.cts.testcore.Timeouts.CALLBACK_NOT_CALLED_TIMEOUT_MS;
+import static android.autofillservice.cts.testcore.Timeouts.CONNECTION_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillManager.AutofillCallback;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.Timeout;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link AutofillCallback} used to recover events during tests.
+ */
+public final class MyAutofillCallback extends AutofillCallback {
+
+    private static final String TAG = "MyAutofillCallback";
+    private final BlockingQueue<MyEvent> mEvents = new LinkedBlockingQueue<>();
+
+    public static final Timeout MY_TIMEOUT = CONNECTION_TIMEOUT;
+
+    // We must handle all requests in a separate thread as the service's main thread is the also
+    // the UI thread of the test process and we don't want to hose it in case of failures here
+    private static final HandlerThread sMyThread = new HandlerThread("MyCallbackThread");
+    private final Handler mHandler;
+
+    static {
+        Log.i(TAG, "Starting thread " + sMyThread);
+        sMyThread.start();
+    }
+
+    public MyAutofillCallback() {
+        mHandler = Handler.createAsync(sMyThread.getLooper());
+    }
+
+    @Override
+    public void onAutofillEvent(View view, int event) {
+        mHandler.post(() -> offer(new MyEvent(view, event)));
+    }
+
+    @Override
+    public void onAutofillEvent(View view, int childId, int event) {
+        mHandler.post(() -> offer(new MyEvent(view, childId, event)));
+    }
+
+    private void offer(MyEvent event) {
+        Log.v(TAG, "offer: " + event);
+        Helper.offer(mEvents, event, MY_TIMEOUT.ms());
+    }
+
+    /**
+     * Gets the next available event or fail if it times out.
+     */
+    public MyEvent getEvent() throws InterruptedException {
+        final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        if (event == null) {
+            throw new RetryableException(CONNECTION_TIMEOUT, "no event");
+        }
+        return event;
+    }
+
+    /**
+     * Assert no more events were received.
+     */
+    public void assertNotCalled() throws InterruptedException {
+        final MyEvent event = mEvents.poll(CALLBACK_NOT_CALLED_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        if (event != null) {
+            // Not retryable.
+            throw new IllegalStateException("should not have received " + event);
+        }
+    }
+
+    /**
+     * Used to assert there is no event left behind.
+     */
+    public void assertNumberUnhandledEvents(int expected) {
+        assertWithMessage("Invalid number of events left: %s", mEvents).that(mEvents.size())
+                .isEqualTo(expected);
+    }
+
+    /**
+     * Convenience method to assert an UI shown event for the given view was received.
+     */
+    public MyEvent assertUiShownEvent(View expectedView) throws InterruptedException {
+        final MyEvent event = getEvent();
+        assertWithMessage("Invalid type on event %s", event).that(event.event)
+                .isEqualTo(EVENT_INPUT_SHOWN);
+        assertWithMessage("Invalid view on event %s", event).that(event.view)
+            .isSameInstanceAs(expectedView);
+        return event;
+    }
+
+    /**
+     * Convenience method to assert an UI shown event for the given virtual view was received.
+     */
+    public void assertUiShownEvent(View expectedView, int expectedChildId)
+            throws InterruptedException {
+        final MyEvent event = assertUiShownEvent(expectedView);
+        assertWithMessage("Invalid child on event %s", event).that(event.childId)
+            .isEqualTo(expectedChildId);
+    }
+
+    /**
+     * Convenience method to assert an UI shown event a virtual view was received.
+     *
+     * @return virtual child id
+     */
+    public int assertUiShownEventForVirtualChild(View expectedView) throws InterruptedException {
+        final MyEvent event = assertUiShownEvent(expectedView);
+        return event.childId;
+    }
+
+    /**
+     * Convenience method to assert an UI hidden event for the given view was received.
+     */
+    public MyEvent assertUiHiddenEvent(View expectedView) throws InterruptedException {
+        final MyEvent event = getEvent();
+        assertWithMessage("Invalid type on event %s", event).that(event.event)
+                .isEqualTo(EVENT_INPUT_HIDDEN);
+        assertWithMessage("Invalid view on event %s", event).that(event.view)
+                .isSameInstanceAs(expectedView);
+        return event;
+    }
+
+    /**
+     * Convenience method to assert an UI hidden event for the given view was received.
+     */
+    public void assertUiHiddenEvent(View expectedView, int expectedChildId)
+            throws InterruptedException {
+        final MyEvent event = assertUiHiddenEvent(expectedView);
+        assertWithMessage("Invalid child on event %s", event).that(event.childId)
+                .isEqualTo(expectedChildId);
+    }
+
+    /**
+     * Convenience method to assert an UI unavailable event for the given view was received.
+     */
+    public MyEvent assertUiUnavailableEvent(View expectedView) throws InterruptedException {
+        final MyEvent event = getEvent();
+        assertWithMessage("Invalid type on event %s", event).that(event.event)
+                .isEqualTo(EVENT_INPUT_UNAVAILABLE);
+        assertWithMessage("Invalid view on event %s", event).that(event.view)
+                .isSameInstanceAs(expectedView);
+        return event;
+    }
+
+    /**
+     * Convenience method to assert an UI unavailable event for the given view was received.
+     */
+    public void assertUiUnavailableEvent(View expectedView, int expectedChildId)
+            throws InterruptedException {
+        final MyEvent event = assertUiUnavailableEvent(expectedView);
+        assertWithMessage("Invalid child on event %s", event).that(event.childId)
+                .isEqualTo(expectedChildId);
+    }
+
+    private static final class MyEvent {
+        public final View view;
+        public final int childId;
+        public final int event;
+
+        MyEvent(View view, int event) {
+            this(view, View.NO_ID, event);
+        }
+
+        MyEvent(View view, int childId, int event) {
+            this.view = view;
+            this.childId = childId;
+            this.event = event;
+        }
+
+        @Override
+        public String toString() {
+            return callbackEventAsString(event) + ": " + view + " (childId: " + childId + ")";
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillId.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillId.java
new file mode 100644
index 0000000..8e15b0a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillId.java
@@ -0,0 +1,80 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.autofill.AutofillId;
+
+public final class MyAutofillId implements Parcelable {
+
+    private final AutofillId mId;
+
+    public MyAutofillId(AutofillId id) {
+        mId = id;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        MyAutofillId other = (MyAutofillId) obj;
+        if (mId == null) {
+            if (other.mId != null) return false;
+        } else if (!mId.equals(other.mId)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return mId.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mId, flags);
+    }
+
+    public static final Creator<MyAutofillId> CREATOR = new Creator<MyAutofillId>() {
+
+        @Override
+        public MyAutofillId createFromParcel(Parcel source) {
+            return new MyAutofillId(source.readParcelable(null));
+        }
+
+        @Override
+        public MyAutofillId[] newArray(int size) {
+            return new MyAutofillId[size];
+        }
+    };
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MyDrawable.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyDrawable.java
new file mode 100644
index 0000000..7cc5a9d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyDrawable.java
@@ -0,0 +1,102 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class MyDrawable extends Drawable {
+
+    private static final String TAG = "MyDrawable";
+
+    private static CountDownLatch sLatch;
+    private static MyDrawable sInstance;
+
+    private static Rect sAutofilledBounds;
+
+    public MyDrawable() {
+        if (sInstance != null) {
+            throw new IllegalStateException("There can be only one!");
+        }
+        sInstance = this;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (sInstance != null && sAutofilledBounds == null) {
+            sAutofilledBounds = new Rect(getBounds());
+            Log.d(TAG, "Autofilled at " + sAutofilledBounds);
+            sLatch.countDown();
+        }
+    }
+
+    public static Rect getAutofilledBounds() throws InterruptedException {
+        if (sLatch == null) {
+            throw new AssertionError("sLatch should be not null");
+        }
+
+        if (!sLatch.await(Timeouts.FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(Timeouts.FILL_TIMEOUT, "custom drawable not drawn");
+        }
+        return sAutofilledBounds;
+    }
+
+    /**
+     * Asserts the custom drawable is not drawn.
+     */
+    public static void assertDrawableNotDrawn() throws Exception {
+        if (sLatch == null) {
+            throw new AssertionError("sLatch should be not null");
+        }
+
+        if (sLatch.await(Timeouts.DRAWABLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            throw new AssertionError("custom drawable is drawn");
+        }
+    }
+
+    public static void initStatus() {
+        sLatch = new CountDownLatch(1);
+        sInstance = null;
+        sAutofilledBounds = null;
+    }
+
+    public static void clearStatus() {
+        sLatch = null;
+        sInstance = null;
+        sAutofilledBounds = null;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+    }
+
+    @Override
+    public int getOpacity() {
+        return 0;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/NoOpAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/NoOpAutofillService.java
new file mode 100644
index 0000000..9f098ab
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/NoOpAutofillService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.util.Log;
+
+/**
+ * {@link AutofillService} implementation that does not do anything...
+ */
+public class NoOpAutofillService extends AutofillService {
+
+    private static final String TAG = "NoOpAutofillService";
+
+    public static final String SERVICE_LABEL = "NoOpAutofillService";
+
+    public static final String SERVICE_NAME =
+            "android.autofillservice.cts/.testcore." + NoOpAutofillService.class.getSimpleName();
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+        Log.d(TAG, "onFillRequest()");
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        Log.d(TAG, "onFillResponse()");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCancellationSignalListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCancellationSignalListener.java
new file mode 100644
index 0000000..9670665
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCancellationSignalListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.CancellationSignal;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link android.os.CancellationSignal.OnCancelListener} used to assert that
+ * {@link android.os.CancellationSignal.OnCancelListener} was called, and just once.
+ */
+public final class OneTimeCancellationSignalListener
+        implements CancellationSignal.OnCancelListener {
+    private final CountDownLatch mLatch = new CountDownLatch(1);
+    private final long mTimeoutMs;
+
+    public OneTimeCancellationSignalListener(long timeoutMs) {
+        mTimeoutMs = timeoutMs;
+    }
+
+    public void assertOnCancelCalled() throws Exception {
+        final boolean called = mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) waiting for onCancel()", mTimeoutMs)
+                .that(called).isTrue();
+    }
+
+    @Override
+    public void onCancel() {
+        mLatch.countDown();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCompoundButtonListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCompoundButtonListener.java
new file mode 100644
index 0000000..2d67211
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCompoundButtonListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.widget.CompoundButton;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link android.widget.CompoundButton.OnCheckedChangeListener} used to assert a
+ * {@link CompoundButton} was auto-filled properly.
+ */
+public final class OneTimeCompoundButtonListener implements CompoundButton.OnCheckedChangeListener {
+    private final String name;
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private final CompoundButton button;
+    private final boolean expected;
+
+    public OneTimeCompoundButtonListener(String name, CompoundButton button,
+            boolean expectedAutofillValue) {
+        this.name = name;
+        this.button = button;
+        this.expected = expectedAutofillValue;
+    }
+
+    @Override
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        latch.countDown();
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on CompoundButton %s", FILL_TIMEOUT.ms(), name)
+            .that(set).isTrue();
+        final boolean actual = button.isChecked();
+        assertWithMessage("Wrong auto-fill value on CompoundButton %s", name)
+            .that(actual).isEqualTo(expected);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeDateListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeDateListener.java
new file mode 100644
index 0000000..9dca760
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeDateListener.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.widget.DatePicker;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@OnDateChangedListener} used to assert a {@link DatePicker} was auto-filled properly.
+ */
+public final class OneTimeDateListener implements DatePicker.OnDateChangedListener {
+    private final String name;
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private final DatePicker datePicker;
+    private final int expectedYear;
+    private final int expectedMonth;
+    private final int expectedDay;
+
+    public OneTimeDateListener(String name, DatePicker datePicker, int expectedYear,
+            int expectedMonth,
+            int expectedDay) {
+        this.name = name;
+        this.datePicker = datePicker;
+        this.expectedYear = expectedYear;
+        this.expectedMonth = expectedMonth;
+        this.expectedDay = expectedDay;
+    }
+
+    @Override
+    public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+        latch.countDown();
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on DatePicker %s", FILL_TIMEOUT.ms(), name)
+            .that(set).isTrue();
+        assertWithMessage("Wrong year on DatePicker %s", name)
+            .that(datePicker.getYear()).isEqualTo(expectedYear);
+        assertWithMessage("Wrong month on DatePicker %s", name)
+            .that(datePicker.getMonth()).isEqualTo(expectedMonth);
+        assertWithMessage("Wrong day on DatePicker %s", name)
+            .that(datePicker.getDayOfMonth()).isEqualTo(expectedDay);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeRadioGroupListener.java
new file mode 100644
index 0000000..65d8ded
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeRadioGroupListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.widget.RadioGroup;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link android.widget.RadioGroup.OnCheckedChangeListener} used to assert an
+ * {@link RadioGroup} was auto-filled properly.
+ */
+public final class OneTimeRadioGroupListener implements RadioGroup.OnCheckedChangeListener {
+    private final String name;
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private final RadioGroup radioGroup;
+    private final int expected;
+
+    public OneTimeRadioGroupListener(String name, RadioGroup radioGroup,
+            int expectedAutoFilledValue) {
+        this.name = name;
+        this.radioGroup = radioGroup;
+        this.expected = expectedAutoFilledValue;
+    }
+
+    @Override
+    public void onCheckedChanged(RadioGroup group, int checkedId) {
+        latch.countDown();
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), name)
+            .that(set).isTrue();
+        final int actual = radioGroup.getCheckedRadioButtonId();
+        assertWithMessage("Wrong auto-fill value on RadioGroup %s", name)
+            .that(actual).isEqualTo(expected);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeSpinnerListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeSpinnerListener.java
new file mode 100644
index 0000000..1ca2cb6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeSpinnerListener.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.Spinner;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link OnItemSelectedListener} used to assert an {@link Spinner} was auto-filled properly.
+ */
+public final class OneTimeSpinnerListener implements OnItemSelectedListener {
+    private final String name;
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private final Spinner spinner;
+    private final int expected;
+
+    public OneTimeSpinnerListener(String name, Spinner spinner, int expectedAutoFilledValue) {
+        this.name = name;
+        this.spinner = spinner;
+        this.expected = expectedAutoFilledValue;
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on Spinner %s", FILL_TIMEOUT.ms(), name)
+            .that(set).isTrue();
+        final int actual = spinner.getSelectedItemPosition();
+        assertWithMessage("Wrong auto-fill value on Spinner %s", name)
+            .that(actual).isEqualTo(expected);
+    }
+
+    @Override
+    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+        latch.countDown();
+    }
+
+    @Override
+    public void onNothingSelected(AdapterView<?> parent) {
+        latch.countDown();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeTextWatcher.java
new file mode 100644
index 0000000..35fdf49
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeTextWatcher.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import android.text.TextWatcher;
+import android.widget.EditText;
+
+/**
+ * Custom {@link TextWatcher} used to assert a {@link EditText} was auto-filled properly.
+ */
+public final class OneTimeTextWatcher extends MultipleTimesTextWatcher {
+
+    public OneTimeTextWatcher(String name, EditText editText, CharSequence expectedAutofillValue) {
+        super(name, 1, editText, expectedAutofillValue);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OutOfProcessLoginActivityFinisherReceiver.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OutOfProcessLoginActivityFinisherReceiver.java
new file mode 100644
index 0000000..09db20d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OutOfProcessLoginActivityFinisherReceiver.java
@@ -0,0 +1,36 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import android.autofillservice.cts.activities.OutOfProcessLoginActivity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * A {@link BroadcastReceiver} that finishes {@link OutOfProcessLoginActivity}.
+ */
+public class OutOfProcessLoginActivityFinisherReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "OutOfProcessLoginActivityFinisherReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "Goodbye, unfinished business!");
+        OutOfProcessLoginActivity.finishIt();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/SelfDestructReceiver.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/SelfDestructReceiver.java
new file mode 100644
index 0000000..ba99654
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/SelfDestructReceiver.java
@@ -0,0 +1,36 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+import android.util.Log;
+
+/**
+ * A {@link BroadcastReceiver} that kills its process.
+ */
+public class SelfDestructReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "SelfDestructReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "Goodbye, cruel world!");
+        Process.killProcess(Process.myPid());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Timeouts.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Timeouts.java
new file mode 100644
index 0000000..7d4e140
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Timeouts.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import com.android.compatibility.common.util.Timeout;
+
+/**
+ * Timeouts for common tasks.
+ */
+public final class Timeouts {
+
+    private static final long ONE_TIMEOUT_TO_RULE_THEN_ALL_MS = 20_000;
+    private static final long ONE_NAPTIME_TO_RULE_THEN_ALL_MS = 2_000;
+
+    public static final long MOCK_IME_TIMEOUT_MS = 5_000;
+    public static final long DRAWABLE_TIMEOUT_MS = 5_000;
+
+    public static final long LONG_PRESS_MS = 3000;
+    public static final long RESPONSE_DELAY_MS = 1000;
+
+    /**
+     * Timeout until framework binds / unbinds from service.
+     */
+    public static final Timeout CONNECTION_TIMEOUT = new Timeout("CONNECTION_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for {@link MyAutofillCallback#assertNotCalled()} - test will sleep for that amount of
+     * time as there is no callback that be received to assert it's not shown.
+     */
+    public static final long CALLBACK_NOT_CALLED_TIMEOUT_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+
+    /**
+     * Timeout until framework unbinds from a service.
+     */
+    // TODO: must be higher than RemoteFillService.TIMEOUT_IDLE_BIND_MILLIS, so we should use a
+    // @hidden @Testing constants instead...
+    public static final Timeout IDLE_UNBIND_TIMEOUT = new Timeout("IDLE_UNBIND_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout to get the expected number of fill events.
+     */
+    public static final Timeout FILL_EVENTS_TIMEOUT = new Timeout("FILL_EVENTS_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for expected autofill requests.
+     */
+    public static final Timeout FILL_TIMEOUT = new Timeout("FILL_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for expected save requests.
+     */
+    public static final Timeout SAVE_TIMEOUT = new Timeout("SAVE_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout used when save is not expected to be shown - test will sleep for that amount of time
+     * as there is no callback that be received to assert it's not shown.
+     */
+    public static final long SAVE_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+
+    /**
+     * Timeout for UI operations. Typically used by {@link UiBot}.
+     */
+    public static final Timeout UI_TIMEOUT = new Timeout("UI_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for a11y window change events.
+     */
+    public static final long WINDOW_CHANGE_TIMEOUT_MS = ONE_TIMEOUT_TO_RULE_THEN_ALL_MS;
+
+    /**
+     * Timeout used when an a11y window change events is not expected to be generated - test will
+     * sleep for that amount of time as there is no callback that be received to assert it's not
+     * shown.
+     */
+    public static final long WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS =
+            ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+
+    /**
+     * Timeout for webview operations. Typically used by {@link UiBot}.
+     */
+    // TODO(b/80317628): switch back to ONE_TIMEOUT_TO_RULE_THEN_ALL_MS once fixed...
+    public static final Timeout WEBVIEW_TIMEOUT = new Timeout("WEBVIEW_TIMEOUT", 3_000, 2F, 5_000);
+
+    /**
+     * Timeout for showing the autofill dataset picker UI.
+     *
+     * <p>The value is usually higher than {@link #UI_TIMEOUT} because the performance of the
+     * dataset picker UI can be affect by external factors in some low-level devices.
+     *
+     * <p>Typically used by {@link UiBot}.
+     */
+    public static final Timeout UI_DATASET_PICKER_TIMEOUT = new Timeout("UI_DATASET_PICKER_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout used when the dataset picker is not expected to be shown - test will sleep for that
+     * amount of time as there is no callback that be received to assert it's not shown.
+     */
+    public static final long DATASET_PICKER_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+
+    /**
+     * Timeout (in milliseconds) for an activity to be brought out to top.
+     */
+    public static final Timeout ACTIVITY_RESURRECTION = new Timeout("ACTIVITY_RESURRECTION",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for changing the screen orientation.
+     */
+    public static final Timeout UI_SCREEN_ORIENTATION_TIMEOUT = new Timeout(
+            "UI_SCREEN_ORIENTATION_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F,
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    private Timeouts() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
new file mode 100644
index 0000000..91b5dfa
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
@@ -0,0 +1,1279 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.DATASET_PICKER_NOT_SHOWN_NAPTIME_MS;
+import static android.autofillservice.cts.testcore.Timeouts.LONG_PRESS_MS;
+import static android.autofillservice.cts.testcore.Timeouts.SAVE_NOT_SHOWN_NAPTIME_MS;
+import static android.autofillservice.cts.testcore.Timeouts.SAVE_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.UI_DATASET_PICKER_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.UI_SCREEN_ORIENTATION_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.UI_TIMEOUT;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.SearchCondition;
+import android.support.test.uiautomator.StaleObjectException;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.style.URLSpan;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.Timeout;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Helper for UI-related needs.
+ */
+public class UiBot {
+
+    private static final String TAG = "AutoFillCtsUiBot";
+
+    private static final String RESOURCE_ID_DATASET_PICKER = "autofill_dataset_picker";
+    private static final String RESOURCE_ID_DATASET_HEADER = "autofill_dataset_header";
+    private static final String RESOURCE_ID_SAVE_SNACKBAR = "autofill_save";
+    private static final String RESOURCE_ID_SAVE_ICON = "autofill_save_icon";
+    private static final String RESOURCE_ID_SAVE_TITLE = "autofill_save_title";
+    private static final String RESOURCE_ID_CONTEXT_MENUITEM = "floating_toolbar_menu_item_text";
+    private static final String RESOURCE_ID_SAVE_BUTTON_NO = "autofill_save_no";
+    private static final String RESOURCE_ID_SAVE_BUTTON_YES = "autofill_save_yes";
+    private static final String RESOURCE_ID_OVERFLOW = "overflow";
+
+    private static final String RESOURCE_STRING_SAVE_TITLE = "autofill_save_title";
+    private static final String RESOURCE_STRING_SAVE_TITLE_WITH_TYPE =
+            "autofill_save_title_with_type";
+    private static final String RESOURCE_STRING_SAVE_TYPE_PASSWORD = "autofill_save_type_password";
+    private static final String RESOURCE_STRING_SAVE_TYPE_ADDRESS = "autofill_save_type_address";
+    private static final String RESOURCE_STRING_SAVE_TYPE_CREDIT_CARD =
+            "autofill_save_type_credit_card";
+    private static final String RESOURCE_STRING_SAVE_TYPE_USERNAME = "autofill_save_type_username";
+    private static final String RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS =
+            "autofill_save_type_email_address";
+    private static final String RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD =
+            "autofill_save_type_debit_card";
+    private static final String RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD =
+            "autofill_save_type_payment_card";
+    private static final String RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD =
+            "autofill_save_type_generic_card";
+    private static final String RESOURCE_STRING_SAVE_BUTTON_NEVER = "autofill_save_never";
+    private static final String RESOURCE_STRING_SAVE_BUTTON_NOT_NOW = "autofill_save_notnow";
+    private static final String RESOURCE_STRING_SAVE_BUTTON_NO_THANKS = "autofill_save_no";
+    private static final String RESOURCE_STRING_SAVE_BUTTON_YES = "autofill_save_yes";
+    private static final String RESOURCE_STRING_UPDATE_BUTTON_YES = "autofill_update_yes";
+    private static final String RESOURCE_STRING_CONTINUE_BUTTON_YES = "autofill_continue_yes";
+    private static final String RESOURCE_STRING_UPDATE_TITLE = "autofill_update_title";
+    private static final String RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE =
+            "autofill_update_title_with_type";
+
+    private static final String RESOURCE_STRING_AUTOFILL = "autofill";
+    private static final String RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE =
+            "autofill_picker_accessibility_title";
+    private static final String RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE =
+            "autofill_save_accessibility_title";
+
+
+    static final BySelector DATASET_PICKER_SELECTOR = By.res("android", RESOURCE_ID_DATASET_PICKER);
+    private static final BySelector SAVE_UI_SELECTOR = By.res("android", RESOURCE_ID_SAVE_SNACKBAR);
+    private static final BySelector DATASET_HEADER_SELECTOR =
+            By.res("android", RESOURCE_ID_DATASET_HEADER);
+
+    // TODO: figure out a more reliable solution that does not depend on SystemUI resources.
+    private static final String SPLIT_WINDOW_DIVIDER_ID =
+            "com.android.systemui:id/docked_divider_background";
+
+    private static final boolean DUMP_ON_ERROR = true;
+
+    private static final int MAX_UIOBJECT_RETRY_COUNT = 3;
+
+    /** Pass to {@link #setScreenOrientation(int)} to change the display to portrait mode */
+    public static int PORTRAIT = 0;
+
+    /** Pass to {@link #setScreenOrientation(int)} to change the display to landscape mode */
+    public static int LANDSCAPE = 1;
+
+    private final UiDevice mDevice;
+    private final Context mContext;
+    private final String mPackageName;
+    private final UiAutomation mAutoman;
+    private final Timeout mDefaultTimeout;
+
+    private boolean mOkToCallAssertNoDatasets;
+
+    public UiBot() {
+        this(UI_TIMEOUT);
+    }
+
+    public UiBot(Timeout defaultTimeout) {
+        mDefaultTimeout = defaultTimeout;
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        mDevice = UiDevice.getInstance(instrumentation);
+        mContext = instrumentation.getContext();
+        mPackageName = mContext.getPackageName();
+        mAutoman = instrumentation.getUiAutomation();
+    }
+
+    public void waitForIdle() {
+        final long before = SystemClock.elapsedRealtimeNanos();
+        mDevice.waitForIdle();
+        final float delta = ((float) (SystemClock.elapsedRealtimeNanos() - before)) / 1_000_000;
+        Log.v(TAG, "device idle in " + delta + "ms");
+    }
+
+    public void waitForIdleSync() {
+        final long before = SystemClock.elapsedRealtimeNanos();
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        final float delta = ((float) (SystemClock.elapsedRealtimeNanos() - before)) / 1_000_000;
+        Log.v(TAG, "device idle sync in " + delta + "ms");
+    }
+
+    public void reset() {
+        mOkToCallAssertNoDatasets = false;
+    }
+
+    /**
+     * Assumes the device has a minimum height and width of {@code minSize}, throwing a
+     * {@code AssumptionViolatedException} if it doesn't (so the test is skiped by the JUnit
+     * Runner).
+     */
+    public void assumeMinimumResolution(int minSize) {
+        final int width = mDevice.getDisplayWidth();
+        final int heigth = mDevice.getDisplayHeight();
+        final int min = Math.min(width, heigth);
+        assumeTrue("Screen size is too small (" + width + "x" + heigth + ")", min >= minSize);
+        Log.d(TAG, "assumeMinimumResolution(" + minSize + ") passed: screen size is "
+                + width + "x" + heigth);
+    }
+
+    /**
+     * Sets the screen resolution in a way that the IME doesn't interfere with the Autofill UI
+     * when the device is rotated to landscape.
+     *
+     * When called, test must call <p>{@link #resetScreenResolution()} in a {@code finally} block.
+     *
+     * @deprecated this method should not be necessarily anymore as we're using a MockIme.
+     */
+    @Deprecated
+    // TODO: remove once we're sure no more OEM is getting failure due to screen size
+    public void setScreenResolution() {
+        if (true) {
+            Log.w(TAG, "setScreenResolution(): ignored");
+            return;
+        }
+        assumeMinimumResolution(500);
+
+        runShellCommand("wm size 1080x1920");
+        runShellCommand("wm density 320");
+    }
+
+    /**
+     * Resets the screen resolution.
+     *
+     * <p>Should always be called after {@link #setScreenResolution()}.
+     *
+     * @deprecated this method should not be necessarily anymore as we're using a MockIme.
+     */
+    @Deprecated
+    // TODO: remove once we're sure no more OEM is getting failure due to screen size
+    public void resetScreenResolution() {
+        if (true) {
+            Log.w(TAG, "resetScreenResolution(): ignored");
+            return;
+        }
+        runShellCommand("wm density reset");
+        runShellCommand("wm size reset");
+    }
+
+    /**
+     * Asserts the dataset picker is not shown anymore.
+     *
+     * @throws IllegalStateException if called *before* an assertion was made to make sure the
+     * dataset picker is shown - if that's not the case, call
+     * {@link #assertNoDatasetsEver()} instead.
+     */
+    public void assertNoDatasets() throws Exception {
+        if (!mOkToCallAssertNoDatasets) {
+            throw new IllegalStateException(
+                    "Cannot call assertNoDatasets() without calling assertDatasets first");
+        }
+        mDevice.wait(Until.gone(DATASET_PICKER_SELECTOR), UI_DATASET_PICKER_TIMEOUT.ms());
+        mOkToCallAssertNoDatasets = false;
+    }
+
+    /**
+     * Asserts the dataset picker was never shown.
+     *
+     * <p>This method is slower than {@link #assertNoDatasets()} and should only be called in the
+     * cases where the dataset picker was not previous shown.
+     */
+    public void assertNoDatasetsEver() throws Exception {
+        assertNeverShown("dataset picker", DATASET_PICKER_SELECTOR,
+                DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
+    }
+
+    /**
+     * Asserts the dataset chooser is shown and contains exactly the given datasets.
+     *
+     * @return the dataset picker object.
+     */
+    public UiObject2 assertDatasets(String...names) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        return assertDatasets(picker, names);
+    }
+
+    protected UiObject2 assertDatasets(UiObject2 picker, String...names) {
+        assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
+                .containsExactlyElementsIn(Arrays.asList(names)).inOrder();
+        return picker;
+    }
+
+    /**
+     * Asserts the dataset chooser is shown and contains the given datasets.
+     *
+     * @return the dataset picker object.
+     */
+    public UiObject2 assertDatasetsContains(String...names) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
+                .containsAtLeastElementsIn(Arrays.asList(names)).inOrder();
+        return picker;
+    }
+
+    /**
+     * Asserts the dataset chooser is shown and contains the given datasets, header, and footer.
+     * <p>In fullscreen, header view is not under R.id.autofill_dataset_picker.
+     *
+     * @return the dataset picker object.
+     */
+    public UiObject2 assertDatasetsWithBorders(String header, String footer, String...names)
+            throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        final List<String> expectedChild = new ArrayList<>();
+        if (header != null) {
+            if (Helper.isAutofillWindowFullScreen(mContext)) {
+                final UiObject2 headerView = waitForObject(DATASET_HEADER_SELECTOR,
+                        UI_DATASET_PICKER_TIMEOUT);
+                assertWithMessage("fullscreen wrong dataset header")
+                        .that(getChildrenAsText(headerView))
+                        .containsExactlyElementsIn(Arrays.asList(header)).inOrder();
+            } else {
+                expectedChild.add(header);
+            }
+        }
+        expectedChild.addAll(Arrays.asList(names));
+        if (footer != null) {
+            expectedChild.add(footer);
+        }
+        assertWithMessage("wrong elements on dataset picker").that(getChildrenAsText(picker))
+                .containsExactlyElementsIn(expectedChild).inOrder();
+        return picker;
+    }
+
+    /**
+     * Gets the text of this object children.
+     */
+    public List<String> getChildrenAsText(UiObject2 object) {
+        final List<String> list = new ArrayList<>();
+        getChildrenAsText(object, list);
+        return list;
+    }
+
+    private static void getChildrenAsText(UiObject2 object, List<String> children) {
+        final String text = object.getText();
+        if (text != null) {
+            children.add(text);
+        }
+        for (UiObject2 child : object.getChildren()) {
+            getChildrenAsText(child, children);
+        }
+    }
+
+    /**
+     * Selects a dataset that should be visible in the floating UI and does not need to wait for
+     * application become idle.
+     */
+    public void selectDataset(String name) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        selectDataset(picker, name);
+    }
+
+    /**
+     * Selects a dataset that should be visible in the floating UI and waits for application become
+     * idle if needed.
+     */
+    public void selectDatasetSync(String name) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        selectDataset(picker, name);
+        mDevice.waitForIdle();
+    }
+
+    /**
+     * Selects a dataset that should be visible in the floating UI.
+     */
+    public void selectDataset(UiObject2 picker, String name) {
+        final UiObject2 dataset = picker.findObject(By.text(name));
+        if (dataset == null) {
+            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(picker));
+        }
+        dataset.click();
+    }
+
+    /**
+     * Finds the suggestion by name and perform long click on suggestion to trigger attribution
+     * intent.
+     */
+    public void longPressSuggestion(String name) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Asserts the suggestion chooser is shown in the suggestion view.
+     */
+    public void assertSuggestion(String name) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Asserts the suggestion chooser is not shown in the suggestion view.
+     */
+    public void assertNoSuggestion(String name) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Scrolls the suggestion view.
+     *
+     * @param direction The direction to scroll.
+     * @param speed The speed to scroll per second.
+     */
+    public void scrollSuggestionView(Direction direction, int speed) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Selects a view by text.
+     *
+     * <p><b>NOTE:</b> when selecting an option in dataset picker is shown, prefer
+     * {@link #selectDataset(String)}.
+     */
+    public void selectByText(String name) throws Exception {
+        Log.v(TAG, "selectByText(): " + name);
+
+        final UiObject2 object = waitForObject(By.text(name));
+        object.click();
+    }
+
+    /**
+     * Asserts a text is shown.
+     *
+     * <p><b>NOTE:</b> when asserting the dataset picker is shown, prefer
+     * {@link #assertDatasets(String...)}.
+     */
+    public UiObject2 assertShownByText(String text) throws Exception {
+        return assertShownByText(text, mDefaultTimeout);
+    }
+
+    public UiObject2 assertShownByText(String text, Timeout timeout) throws Exception {
+        final UiObject2 object = waitForObject(By.text(text), timeout);
+        assertWithMessage("No node with text '%s'", text).that(object).isNotNull();
+        return object;
+    }
+
+    /**
+     * Finds a node by text, without waiting for it to be shown (but failing if it isn't).
+     */
+    @NonNull
+    public UiObject2 findRightAwayByText(@NonNull String text) throws Exception {
+        final UiObject2 object = mDevice.findObject(By.text(text));
+        assertWithMessage("no UIObject for text '%s'", text).that(object).isNotNull();
+        return object;
+    }
+
+    /**
+     * Asserts that the text is not showing for sure in the screen "as is", i.e., without waiting
+     * for it.
+     *
+     * <p>Typically called after another assertion that waits for a condition to be shown.
+     */
+    public void assertNotShowingForSure(String text) throws Exception {
+        final UiObject2 object = mDevice.findObject(By.text(text));
+        assertWithMessage("Found node with text '%s'", text).that(object).isNull();
+    }
+
+    /**
+     * Asserts a node with the given content description is shown.
+     *
+     */
+    public UiObject2 assertShownByContentDescription(String contentDescription) throws Exception {
+        final UiObject2 object = waitForObject(By.desc(contentDescription));
+        assertWithMessage("No node with content description '%s'", contentDescription).that(object)
+                .isNotNull();
+        return object;
+    }
+
+    /**
+     * Checks if a View with a certain text exists.
+     */
+    public boolean hasViewWithText(String name) {
+        Log.v(TAG, "hasViewWithText(): " + name);
+
+        return mDevice.findObject(By.text(name)) != null;
+    }
+
+    /**
+     * Selects a view by id.
+     */
+    public UiObject2 selectByRelativeId(String id) throws Exception {
+        Log.v(TAG, "selectByRelativeId(): " + id);
+        UiObject2 object = waitForObject(By.res(mPackageName, id));
+        object.click();
+        return object;
+    }
+
+    /**
+     * Asserts the id is shown on the screen.
+     */
+    public UiObject2 assertShownById(String id) throws Exception {
+        final UiObject2 object = waitForObject(By.res(id));
+        assertThat(object).isNotNull();
+        return object;
+    }
+
+    /**
+     * Asserts the id is shown on the screen, using a resource id from the test package.
+     */
+    public UiObject2 assertShownByRelativeId(String id) throws Exception {
+        return assertShownByRelativeId(id, mDefaultTimeout);
+    }
+
+    public UiObject2 assertShownByRelativeId(String id, Timeout timeout) throws Exception {
+        final UiObject2 obj = waitForObject(By.res(mPackageName, id), timeout);
+        assertThat(obj).isNotNull();
+        return obj;
+    }
+
+    /**
+     * Asserts the id is not shown on the screen anymore, using a resource id from the test package.
+     *
+     * <p><b>Note:</b> this method should only called AFTER the id was previously shown, otherwise
+     * it might pass without really asserting anything.
+     */
+    public void assertGoneByRelativeId(@NonNull String id, @NonNull Timeout timeout) {
+        assertGoneByRelativeId(/* parent = */ null, id, timeout);
+    }
+
+    public void assertGoneByRelativeId(int resId, @NonNull Timeout timeout) {
+        assertGoneByRelativeId(/* parent = */ null, getIdName(resId), timeout);
+    }
+
+    private String getIdName(int resId) {
+        return mContext.getResources().getResourceEntryName(resId);
+    }
+
+    /**
+     * Asserts the id is not shown on the parent anymore, using a resource id from the test package.
+     *
+     * <p><b>Note:</b> this method should only called AFTER the id was previously shown, otherwise
+     * it might pass without really asserting anything.
+     */
+    public void assertGoneByRelativeId(@Nullable UiObject2 parent, @NonNull String id,
+            @NonNull Timeout timeout) {
+        final SearchCondition<Boolean> condition = Until.gone(By.res(mPackageName, id));
+        final boolean gone = parent != null
+                ? parent.wait(condition, timeout.ms())
+                : mDevice.wait(condition, timeout.ms());
+        if (!gone) {
+            final String message = "Object with id '" + id + "' should be gone after "
+                    + timeout + " ms";
+            dumpScreen(message);
+            throw new RetryableException(message);
+        }
+    }
+
+    public UiObject2 assertShownByRelativeId(int resId) throws Exception {
+        return assertShownByRelativeId(getIdName(resId));
+    }
+
+    public void assertNeverShownByRelativeId(@NonNull String description, int resId, long timeout)
+            throws Exception {
+        final BySelector selector = By.res(Helper.MY_PACKAGE, getIdName(resId));
+        assertNeverShown(description, selector, timeout);
+    }
+
+    /**
+     * Asserts that a {@code selector} is not showing after {@code timeout} milliseconds.
+     */
+    protected void assertNeverShown(String description, BySelector selector, long timeout)
+            throws Exception {
+        SystemClock.sleep(timeout);
+        final UiObject2 object = mDevice.findObject(selector);
+        if (object != null) {
+            throw new AssertionError(
+                    String.format("Should not be showing %s after %dms, but got %s",
+                            description, timeout, getChildrenAsText(object)));
+        }
+    }
+
+    /**
+     * Gets the text set on a view.
+     */
+    public String getTextByRelativeId(String id) throws Exception {
+        return waitForObject(By.res(mPackageName, id)).getText();
+    }
+
+    /**
+     * Focus in the view with the given resource id.
+     */
+    public void focusByRelativeId(String id) throws Exception {
+        waitForObject(By.res(mPackageName, id)).click();
+    }
+
+    /**
+     * Sets a new text on a view.
+     */
+    public void setTextByRelativeId(String id, String newText) throws Exception {
+        waitForObject(By.res(mPackageName, id)).setText(newText);
+    }
+
+    /**
+     * Asserts the save snackbar is showing and returns it.
+     */
+    public UiObject2 assertSaveShowing(int type) throws Exception {
+        return assertSaveShowing(SAVE_TIMEOUT, type);
+    }
+
+    /**
+     * Asserts the save snackbar is showing and returns it.
+     */
+    public UiObject2 assertSaveShowing(Timeout timeout, int type) throws Exception {
+        return assertSaveShowing(null, timeout, type);
+    }
+
+    /**
+     * Asserts the save snackbar is showing with the Update message and returns it.
+     */
+    public UiObject2 assertUpdateShowing(int... types) throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ true, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                null, SAVE_TIMEOUT, types);
+    }
+
+    /**
+     * Presses the Back button.
+     */
+    public void pressBack() {
+        Log.d(TAG, "pressBack()");
+        mDevice.pressBack();
+    }
+
+    /**
+     * Presses the Home button.
+     */
+    public void pressHome() {
+        Log.d(TAG, "pressHome()");
+        mDevice.pressHome();
+    }
+
+    /**
+     * Asserts the save snackbar is not showing.
+     */
+    public void assertSaveNotShowing(int type) throws Exception {
+        assertNeverShown("save UI for type " + type, SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
+    }
+
+    public void assertSaveNotShowing() throws Exception {
+        assertNeverShown("save UI", SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
+    }
+
+    private String getSaveTypeString(int type) {
+        final String typeResourceName;
+        switch (type) {
+            case SAVE_DATA_TYPE_PASSWORD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_PASSWORD;
+                break;
+            case SAVE_DATA_TYPE_ADDRESS:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_ADDRESS;
+                break;
+            case SAVE_DATA_TYPE_CREDIT_CARD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_CREDIT_CARD;
+                break;
+            case SAVE_DATA_TYPE_USERNAME:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_USERNAME;
+                break;
+            case SAVE_DATA_TYPE_EMAIL_ADDRESS:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS;
+                break;
+            case SAVE_DATA_TYPE_DEBIT_CARD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD;
+                break;
+            case SAVE_DATA_TYPE_PAYMENT_CARD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD;
+                break;
+            case SAVE_DATA_TYPE_GENERIC_CARD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD;
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported type: " + type);
+        }
+        return getString(typeResourceName);
+    }
+
+    public UiObject2 assertSaveShowing(String description, int... types) throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                description, SAVE_TIMEOUT, types);
+    }
+
+    public UiObject2 assertSaveShowing(String description, Timeout timeout, int... types)
+            throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                description, timeout, types);
+    }
+
+    public UiObject2 assertSaveShowing(int negativeButtonStyle, String description,
+            int... types) throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ false, negativeButtonStyle, description,
+                SAVE_TIMEOUT, types);
+    }
+
+    public UiObject2 assertSaveShowing(int positiveButtonStyle, int... types) throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                positiveButtonStyle, /* description= */ null, SAVE_TIMEOUT, types);
+    }
+
+    public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
+            String description, Timeout timeout, int... types) throws Exception {
+        return assertSaveOrUpdateShowing(update, negativeButtonStyle,
+                SaveInfo.POSITIVE_BUTTON_STYLE_SAVE, description, timeout, types);
+    }
+
+    public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
+            int positiveButtonStyle, String description, Timeout timeout, int... types)
+            throws Exception {
+
+        final UiObject2 snackbar = waitForObject(SAVE_UI_SELECTOR, timeout);
+
+        final UiObject2 titleView =
+                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_TITLE), timeout);
+        assertWithMessage("save title (%s) is not shown", RESOURCE_ID_SAVE_TITLE).that(titleView)
+                .isNotNull();
+
+        final UiObject2 iconView =
+                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_ICON), timeout);
+        assertWithMessage("save icon (%s) is not shown", RESOURCE_ID_SAVE_ICON).that(iconView)
+                .isNotNull();
+
+        final String actualTitle = titleView.getText();
+        Log.d(TAG, "save title: " + actualTitle);
+
+        final String titleId, titleWithTypeId;
+        if (update) {
+            titleId = RESOURCE_STRING_UPDATE_TITLE;
+            titleWithTypeId = RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE;
+        } else {
+            titleId = RESOURCE_STRING_SAVE_TITLE;
+            titleWithTypeId = RESOURCE_STRING_SAVE_TITLE_WITH_TYPE;
+        }
+
+        final String serviceLabel = InstrumentedAutoFillService.getServiceLabel();
+        switch (types.length) {
+            case 1:
+                final String expectedTitle = (types[0] == SAVE_DATA_TYPE_GENERIC)
+                        ? Html.fromHtml(getString(titleId, serviceLabel), 0).toString()
+                        : Html.fromHtml(getString(titleWithTypeId,
+                                getSaveTypeString(types[0]), serviceLabel), 0).toString();
+                assertThat(actualTitle).isEqualTo(expectedTitle);
+                break;
+            case 2:
+                // We cannot predict the order...
+                assertThat(actualTitle).contains(getSaveTypeString(types[0]));
+                assertThat(actualTitle).contains(getSaveTypeString(types[1]));
+                break;
+            case 3:
+                // We cannot predict the order...
+                assertThat(actualTitle).contains(getSaveTypeString(types[0]));
+                assertThat(actualTitle).contains(getSaveTypeString(types[1]));
+                assertThat(actualTitle).contains(getSaveTypeString(types[2]));
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid types: " + Arrays.toString(types));
+        }
+
+        if (description != null) {
+            final UiObject2 saveSubTitle = snackbar.findObject(By.text(description));
+            assertWithMessage("save subtitle(%s)", description).that(saveSubTitle).isNotNull();
+        }
+
+        final String positiveButtonStringId;
+        switch (positiveButtonStyle) {
+            case SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE:
+                positiveButtonStringId = RESOURCE_STRING_CONTINUE_BUTTON_YES;
+                break;
+            default:
+                positiveButtonStringId = update ? RESOURCE_STRING_UPDATE_BUTTON_YES
+                        : RESOURCE_STRING_SAVE_BUTTON_YES;
+        }
+        final String expectedPositiveButtonText = getString(positiveButtonStringId).toUpperCase();
+        final UiObject2 positiveButton = waitForObject(snackbar,
+                By.res("android", RESOURCE_ID_SAVE_BUTTON_YES), timeout);
+        assertWithMessage("wrong text on positive button")
+                .that(positiveButton.getText().toUpperCase()).isEqualTo(expectedPositiveButtonText);
+
+        final String negativeButtonStringId;
+        if (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT) {
+            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NOT_NOW;
+        } else if (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_NEVER) {
+            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NEVER;
+        } else {
+            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NO_THANKS;
+        }
+        final String expectedNegativeButtonText = getString(negativeButtonStringId).toUpperCase();
+        final UiObject2 negativeButton = waitForObject(snackbar,
+                By.res("android", RESOURCE_ID_SAVE_BUTTON_NO), timeout);
+        assertWithMessage("wrong text on negative button")
+                .that(negativeButton.getText().toUpperCase()).isEqualTo(expectedNegativeButtonText);
+
+        final String expectedAccessibilityTitle =
+                getString(RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE);
+        assertAccessibilityTitle(snackbar, expectedAccessibilityTitle);
+
+        return snackbar;
+    }
+
+    /**
+     * Taps an option in the save snackbar.
+     *
+     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
+     * @param types expected types of save info.
+     */
+    public void saveForAutofill(boolean yesDoIt, int... types) throws Exception {
+        final UiObject2 saveSnackBar = assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, types);
+        saveForAutofill(saveSnackBar, yesDoIt);
+    }
+
+    public void updateForAutofill(boolean yesDoIt, int... types) throws Exception {
+        final UiObject2 saveUi = assertUpdateShowing(types);
+        saveForAutofill(saveUi, yesDoIt);
+    }
+
+    /**
+     * Taps an option in the save snackbar.
+     *
+     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
+     * @param types expected types of save info.
+     */
+    public void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types)
+            throws Exception {
+        final UiObject2 saveSnackBar = assertSaveShowing(negativeButtonStyle, null, types);
+        saveForAutofill(saveSnackBar, yesDoIt);
+    }
+
+    /**
+     * Taps the positive button in the save snackbar.
+     *
+     * @param types expected types of save info.
+     */
+    public void saveForAutofill(int positiveButtonStyle, int... types) throws Exception {
+        final UiObject2 saveSnackBar = assertSaveShowing(positiveButtonStyle, types);
+        saveForAutofill(saveSnackBar, /* yesDoIt= */ true);
+    }
+
+    /**
+     * Taps an option in the save snackbar.
+     *
+     * @param saveSnackBar Save snackbar, typically obtained through
+     *            {@link #assertSaveShowing(int)}.
+     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
+     */
+    public void saveForAutofill(UiObject2 saveSnackBar, boolean yesDoIt) {
+        final String id = yesDoIt ? "autofill_save_yes" : "autofill_save_no";
+
+        final UiObject2 button = saveSnackBar.findObject(By.res("android", id));
+        assertWithMessage("save button (%s)", id).that(button).isNotNull();
+        button.click();
+    }
+
+    /**
+     * Gets the AUTOFILL contextual menu by long pressing a text field.
+     *
+     * <p><b>NOTE:</b> this method should only be called in scenarios where we explicitly want to
+     * test the overflow menu. For all other scenarios where we want to test manual autofill, it's
+     * better to call {@code AFM.requestAutofill()} directly, because it's less error-prone and
+     * faster.
+     *
+     * @param id resource id of the field.
+     */
+    public UiObject2 getAutofillMenuOption(String id) throws Exception {
+        final UiObject2 field = waitForObject(By.res(mPackageName, id));
+        // TODO: figure out why obj.longClick() doesn't always work
+        field.click(LONG_PRESS_MS);
+
+        List<UiObject2> menuItems = waitForObjects(
+                By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
+        final String expectedText = getAutofillContextualMenuTitle();
+
+        final StringBuffer menuNames = new StringBuffer();
+
+        // Check first menu for AUTOFILL
+        for (UiObject2 menuItem : menuItems) {
+            final String menuName = menuItem.getText();
+            if (menuName.equalsIgnoreCase(expectedText)) {
+                Log.v(TAG, "AUTOFILL found in first menu");
+                return menuItem;
+            }
+            menuNames.append("'").append(menuName).append("' ");
+        }
+
+        menuNames.append(";");
+
+        // First menu does not have AUTOFILL, check overflow
+        final BySelector overflowSelector = By.res("android", RESOURCE_ID_OVERFLOW);
+
+        // Click overflow menu button.
+        final UiObject2 overflowMenu = waitForObject(overflowSelector, mDefaultTimeout);
+        overflowMenu.click();
+
+        // Wait for overflow menu to show.
+        mDevice.wait(Until.gone(overflowSelector), 1000);
+
+        menuItems = waitForObjects(
+                By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
+        for (UiObject2 menuItem : menuItems) {
+            final String menuName = menuItem.getText();
+            if (menuName.equalsIgnoreCase(expectedText)) {
+                Log.v(TAG, "AUTOFILL found in overflow menu");
+                return menuItem;
+            }
+            menuNames.append("'").append(menuName).append("' ");
+        }
+        throw new RetryableException("no '%s' on '%s'", expectedText, menuNames);
+    }
+
+    String getAutofillContextualMenuTitle() {
+        return getString(RESOURCE_STRING_AUTOFILL);
+    }
+
+    /**
+     * Gets a string from the Android resources.
+     */
+    private String getString(String id) {
+        final Resources resources = mContext.getResources();
+        final int stringId = resources.getIdentifier(id, "string", "android");
+        try {
+            return resources.getString(stringId);
+        } catch (Resources.NotFoundException e) {
+            throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
+                    + ": ", e);
+        }
+    }
+
+    /**
+     * Gets a string from the Android resources.
+     */
+    private String getString(String id, Object... formatArgs) {
+        final Resources resources = mContext.getResources();
+        final int stringId = resources.getIdentifier(id, "string", "android");
+        try {
+            return resources.getString(stringId, formatArgs);
+        } catch (Resources.NotFoundException e) {
+            throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
+                    + ": ", e);
+        }
+    }
+
+    /**
+     * Waits for and returns an object.
+     *
+     * @param selector {@link BySelector} that identifies the object.
+     */
+    private UiObject2 waitForObject(BySelector selector) throws Exception {
+        return waitForObject(selector, mDefaultTimeout);
+    }
+
+    /**
+     * Waits for and returns an object.
+     *
+     * @param parent where to find the object (or {@code null} to use device's root).
+     * @param selector {@link BySelector} that identifies the object.
+     * @param timeout timeout in ms.
+     * @param dumpOnError whether the window hierarchy should be dumped if the object is not found.
+     */
+    private UiObject2 waitForObject(UiObject2 parent, BySelector selector, Timeout timeout,
+            boolean dumpOnError) throws Exception {
+        // NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
+        try {
+            return timeout.run("waitForObject(" + selector + ")", () -> {
+                return parent != null
+                        ? parent.findObject(selector)
+                        : mDevice.findObject(selector);
+
+            });
+        } catch (RetryableException e) {
+            if (dumpOnError) {
+                dumpScreen("waitForObject() for " + selector + "on "
+                        + (parent == null ? "mDevice" : parent) + " failed");
+            }
+            throw e;
+        }
+    }
+
+    public UiObject2 waitForObject(@Nullable UiObject2 parent, @NonNull BySelector selector,
+            @NonNull Timeout timeout)
+            throws Exception {
+        return waitForObject(parent, selector, timeout, DUMP_ON_ERROR);
+    }
+
+    /**
+     * Waits for and returns an object.
+     *
+     * @param selector {@link BySelector} that identifies the object.
+     * @param timeout timeout in ms
+     */
+    protected UiObject2 waitForObject(@NonNull BySelector selector, @NonNull Timeout timeout)
+            throws Exception {
+        return waitForObject(/* parent= */ null, selector, timeout);
+    }
+
+    /**
+     * Waits for and returns a child from a parent {@link UiObject2}.
+     */
+    public UiObject2 assertChildText(UiObject2 parent, String resourceId, String expectedText)
+            throws Exception {
+        final UiObject2 child = waitForObject(parent, By.res(mPackageName, resourceId),
+                Timeouts.UI_TIMEOUT);
+        assertWithMessage("wrong text for view '%s'", resourceId).that(child.getText())
+                .isEqualTo(expectedText);
+        return child;
+    }
+
+    /**
+     * Execute a Runnable and wait for {@link AccessibilityEvent#TYPE_WINDOWS_CHANGED} or
+     * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}.
+     */
+    public AccessibilityEvent waitForWindowChange(Runnable runnable, long timeoutMillis) {
+        try {
+            return mAutoman.executeAndWaitForEvent(runnable, (AccessibilityEvent event) -> {
+                switch (event.getEventType()) {
+                    case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+                    case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
+                        return true;
+                    default:
+                        Log.v(TAG, "waitForWindowChange(): ignoring event " + event);
+                }
+                return false;
+            }, timeoutMillis);
+        } catch (TimeoutException e) {
+            throw new WindowChangeTimeoutException(e, timeoutMillis);
+        }
+    }
+
+    public AccessibilityEvent waitForWindowChange(Runnable runnable) {
+        return waitForWindowChange(runnable, Timeouts.WINDOW_CHANGE_TIMEOUT_MS);
+    }
+
+    /**
+     * Waits for and returns a list of objects.
+     *
+     * @param selector {@link BySelector} that identifies the object.
+     * @param timeout timeout in ms
+     */
+    private List<UiObject2> waitForObjects(BySelector selector, Timeout timeout) throws Exception {
+        // NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
+        try {
+            return timeout.run("waitForObject(" + selector + ")", () -> {
+                final List<UiObject2> uiObjects = mDevice.findObjects(selector);
+                if (uiObjects != null && !uiObjects.isEmpty()) {
+                    return uiObjects;
+                }
+                return null;
+
+            });
+
+        } catch (RetryableException e) {
+            dumpScreen("waitForObjects() for " + selector + "failed");
+            throw e;
+        }
+    }
+
+    private UiObject2 findDatasetPicker(Timeout timeout) throws Exception {
+        // The UI element here is flaky. Sometimes the UI automator returns a StateObject.
+        // Retry is put in place here to make sure that we catch the object.
+        UiObject2 picker = null;
+        int retryCount = 0;
+        final String expectedTitle = getString(RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE);
+        while (retryCount < MAX_UIOBJECT_RETRY_COUNT) {
+            try {
+                picker = waitForObject(DATASET_PICKER_SELECTOR, timeout);
+                assertAccessibilityTitle(picker, expectedTitle);
+                break;
+            } catch (StaleObjectException e) {
+                Log.d(TAG, "Retry grabbing view class");
+            }
+            retryCount++;
+        }
+        assertWithMessage(expectedTitle + " not found").that(retryCount).isLessThan(
+                MAX_UIOBJECT_RETRY_COUNT);
+
+        if (picker != null) {
+            mOkToCallAssertNoDatasets = true;
+        }
+
+        return picker;
+    }
+
+    /**
+     * Asserts a given object has the expected accessibility title.
+     */
+    private void assertAccessibilityTitle(UiObject2 object, String expectedTitle) {
+        // TODO: ideally it should get the AccessibilityWindowInfo from the object, but UiAutomator
+        // does not expose that.
+        for (AccessibilityWindowInfo window : mAutoman.getWindows()) {
+            final CharSequence title = window.getTitle();
+            if (title != null && title.toString().equals(expectedTitle)) {
+                return;
+            }
+        }
+        throw new RetryableException("Title '%s' not found for %s", expectedTitle, object);
+    }
+
+    /**
+     * Sets the the screen orientation.
+     *
+     * @param orientation typically {@link #LANDSCAPE} or {@link #PORTRAIT}.
+     *
+     * @throws RetryableException if value didn't change.
+     */
+    public void setScreenOrientation(int orientation) throws Exception {
+        mAutoman.setRotation(orientation);
+
+        UI_SCREEN_ORIENTATION_TIMEOUT.run("setScreenOrientation(" + orientation + ")", () -> {
+            return getScreenOrientation() == orientation ? Boolean.TRUE : null;
+        });
+    }
+
+    /**
+     * Gets the value of the screen orientation.
+     *
+     * @return typically {@link #LANDSCAPE} or {@link #PORTRAIT}.
+     */
+    public int getScreenOrientation() {
+        return mDevice.getDisplayRotation();
+    }
+
+    /**
+     * Dumps the current view hierarchy and take a screenshot and save both locally so they can be
+     * inspected later.
+     */
+    public void dumpScreen(@NonNull String cause) {
+        try {
+            final File file = Helper.createTestFile("hierarchy.xml");
+            if (file == null) return;
+            Log.w(TAG, "Dumping window hierarchy because " + cause + " on " + file);
+            try (FileInputStream fis = new FileInputStream(file)) {
+                mDevice.dumpWindowHierarchy(file);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "error dumping screen on " + cause, e);
+        } finally {
+            takeScreenshotAndSave();
+        }
+    }
+
+    private Rect cropScreenshotWithoutScreenDecoration(Activity activity) {
+        final WindowInsets[] inset = new WindowInsets[1];
+        final View[] rootView = new View[1];
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            rootView[0] = activity.getWindow().getDecorView();
+            inset[0] = rootView[0].getRootWindowInsets();
+        });
+        final int navBarHeight = inset[0].getStableInsetBottom();
+        final int statusBarHeight = inset[0].getStableInsetTop();
+
+        return new Rect(0, statusBarHeight, rootView[0].getWidth(),
+                rootView[0].getHeight() - navBarHeight - statusBarHeight);
+    }
+
+    // TODO(b/74358143): ideally we should take a screenshot limited by the boundaries of the
+    // activity window, so external elements (such as the clock) are filtered out and don't cause
+    // test flakiness when the contents are compared.
+    public Bitmap takeScreenshot() {
+        return takeScreenshotWithRect(null);
+    }
+
+    public Bitmap takeScreenshot(@NonNull Activity activity) {
+        // crop the screenshot without screen decoration to prevent test flakiness.
+        final Rect rect = cropScreenshotWithoutScreenDecoration(activity);
+        return takeScreenshotWithRect(rect);
+    }
+
+    private Bitmap takeScreenshotWithRect(@Nullable Rect r) {
+        final long before = SystemClock.elapsedRealtime();
+        final Bitmap bitmap = mAutoman.takeScreenshot();
+        final long delta = SystemClock.elapsedRealtime() - before;
+        Log.v(TAG, "Screenshot taken in " + delta + "ms");
+        if (r == null) {
+            return bitmap;
+        }
+        try {
+            return Bitmap.createBitmap(bitmap, r.left, r.top, r.right, r.bottom);
+        } finally {
+            if (bitmap != null) {
+                bitmap.recycle();
+            }
+        }
+    }
+
+    /**
+     * Takes a screenshot and save it in the file system for post-mortem analysis.
+     */
+    public void takeScreenshotAndSave() {
+        File file = null;
+        try {
+            file = Helper.createTestFile("screenshot.png");
+            if (file != null) {
+                Log.i(TAG, "Taking screenshot on " + file);
+                final Bitmap screenshot = takeScreenshot();
+                Helper.dumpBitmap(screenshot, file);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error taking screenshot and saving on " + file, e);
+        }
+    }
+
+    /**
+     * Asserts the contents of a child element.
+     *
+     * @param parent parent object
+     * @param childId (relative) resource id of the child
+     * @param assertion if {@code null}, asserts the child does not exist; otherwise, asserts the
+     * child with it.
+     */
+    public void assertChild(@NonNull UiObject2 parent, @NonNull String childId,
+            @Nullable Visitor<UiObject2> assertion) {
+        final UiObject2 child = parent.findObject(By.res(mPackageName, childId));
+        try {
+            if (assertion != null) {
+                assertWithMessage("Didn't find child with id '%s'", childId).that(child)
+                        .isNotNull();
+                try {
+                    assertion.visit(child);
+                } catch (Throwable t) {
+                    throw new AssertionError("Error on child '" + childId + "'", t);
+                }
+            } else {
+                assertWithMessage("Shouldn't find child with id '%s'", childId).that(child)
+                        .isNull();
+            }
+        } catch (RuntimeException | Error e) {
+            dumpScreen("assertChild(" + childId + ") failed: " + e);
+            throw e;
+        }
+    }
+
+    /**
+     * Waits until the window was split to show multiple activities.
+     */
+    public void waitForWindowSplit() throws Exception {
+        try {
+            assertShownById(SPLIT_WINDOW_DIVIDER_ID);
+        } catch (Exception e) {
+            final long timeout = Timeouts.ACTIVITY_RESURRECTION.ms();
+            Log.e(TAG, "Did not find window divider " + SPLIT_WINDOW_DIVIDER_ID + "; waiting "
+                    + timeout + "ms instead");
+            SystemClock.sleep(timeout);
+        }
+    }
+
+    /**
+     * Finds the first {@link URLSpan} on the current screen.
+     */
+    public URLSpan findFirstUrlSpanWithText(String str) throws Exception {
+        final List<AccessibilityNodeInfo> list = mAutoman.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(str);
+        if (list.isEmpty()) {
+            throw new AssertionError("Didn't found AccessibilityNodeInfo with " + str);
+        }
+
+        final AccessibilityNodeInfo text = list.get(0);
+        final CharSequence accessibilityTextWithSpan = text.getText();
+        if (!(accessibilityTextWithSpan instanceof Spanned)) {
+            throw new AssertionError("\"" + text.getViewIdResourceName() + "\" was not a Spanned");
+        }
+
+        final URLSpan[] spans = ((Spanned) accessibilityTextWithSpan)
+                .getSpans(0, accessibilityTextWithSpan.length(), URLSpan.class);
+        return spans[0];
+    }
+
+    public boolean scrollToTextObject(String text) {
+        UiScrollable scroller = new UiScrollable(new UiSelector().scrollable(true));
+        try {
+            // Swipe far away from the edges to avoid triggering navigation gestures
+            scroller.setSwipeDeadZonePercentage(0.25);
+            return scroller.scrollTextIntoView(text);
+        } catch (UiObjectNotFoundException e) {
+            return false;
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Visitor.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Visitor.java
new file mode 100644
index 0000000..b276a81
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Visitor.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+/**
+ * A generic visitor.
+ *
+ * <p>Typically used by activities under test to provide a way to run an action on the view using
+ * the UI thread. Example:
+ * <pre><code>
+ * void onUsername(ViewVisitor<EditText> v) {
+ *     runOnUiThread(() -> v.visit(mUsername));
+ * }
+ * </code></pre>
+ */
+// TODO: move to common code
+public interface Visitor<T> {
+
+    void visit(T object);
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/WindowChangeTimeoutException.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/WindowChangeTimeoutException.java
new file mode 100644
index 0000000..82c2303
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/WindowChangeTimeoutException.java
@@ -0,0 +1,27 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.RetryableException;
+
+public final class WindowChangeTimeoutException extends RetryableException {
+
+    public WindowChangeTimeoutException(@NonNull Throwable cause, long timeoutMillis) {
+        super(cause, "no window change event in %dms", timeoutMillis);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillManagerTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillManagerTest.java
new file mode 100644
index 0000000..b2dc721
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillManagerTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.unittests;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.autofillservice.cts.testcore.Helper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import com.android.compatibility.common.util.SettingsStateKeeperRule;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AutofillManagerTest {
+
+    private static final String TAG = "AutofillManagerTest";
+
+    private static final int AUTOFILL_ENABLE = 1;
+    private static final int AUTOFILL_DISABLE = 2;
+    private static final String OUTSIDE_QUERYAUTOFILLSTATUS_APK =
+            "TestAutofillServiceApp.apk";
+
+    private static final Context sContext =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    @ClassRule
+    public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
+            new SettingsStateKeeperRule(sContext, Settings.Secure.AUTOFILL_SERVICE);
+
+    @Test
+    @AppModeFull(reason = "Package cannot install in instant app mode")
+    public void testHasEnabledAutofillServices() throws Exception {
+        // Verify the calling application's AutofillService is initially disabled
+        runQueryAutofillStatusActivityAndVerifyResult(AUTOFILL_DISABLE);
+
+        // Enable calling application's AutofillService
+        enableOutsidePackageTestAutofillService();
+
+        // Verify the calling application's AutofillService is enabled
+        runQueryAutofillStatusActivityAndVerifyResult(AUTOFILL_ENABLE);
+
+        // Update the calling application package and verify the calling application's
+        // AutofillService is still enabled
+        install(OUTSIDE_QUERYAUTOFILLSTATUS_APK);
+        runQueryAutofillStatusActivityAndVerifyResult(AUTOFILL_ENABLE);
+    }
+
+    private void enableOutsidePackageTestAutofillService() {
+        final String outsidePackageAutofillServiceName =
+                "android.autofill.cts2/.NoOpAutofillService";
+        Helper.enableAutofillService(sContext, outsidePackageAutofillServiceName);
+    }
+
+    private void install(String apk) {
+        final String installResult = runShellCommand(
+                "pm install -r /data/local/tmp/cts/autofill/" + apk);
+        Log.d(TAG, "install result = " + installResult);
+        assertThat(installResult.trim()).isEqualTo("Success");
+    }
+
+    /**
+     * Start an activity that uses hasEnabledAutofillServices() to query its AutofillService
+     * status and return the status result to the caller. Then we verify the status result from
+     * the Activity.
+     */
+    private void runQueryAutofillStatusActivityAndVerifyResult(int expectedStatus) {
+        final String actionAutofillStatusActivityFinish =
+                "ACTION_AUTOFILL_STATUS_ACTIVITY_FINISH_" + SystemClock.uptimeMillis();
+
+        // register a activity finish receiver
+        final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(sContext,
+                actionAutofillStatusActivityFinish);
+        receiver.register();
+
+        // Start an Activity from another package
+        final Intent outsideActivity = new Intent();
+        outsideActivity.setComponent(new ComponentName("android.autofill.cts2",
+                "android.autofill.cts2.QueryAutofillStatusActivity"));
+        outsideActivity.setFlags(FLAG_ACTIVITY_NEW_TASK);
+        final Intent broadcastIntent = new Intent(actionAutofillStatusActivityFinish);
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(sContext, 0, broadcastIntent,
+                0);
+        outsideActivity.putExtra("finishBroadcast", pendingIntent);
+        sContext.startActivity(outsideActivity);
+
+        // Verify the finish broadcast is received.
+        final Intent intent = receiver.awaitForBroadcast();
+        assertThat(intent).isNotNull();
+        // Verify the status result code.
+        final int statusResultCode = receiver.getResultCode();
+        Log.d(TAG, "hasEnabledAutofillServices statusResultCode = " + statusResultCode);
+        assertThat(statusResultCode).isEqualTo(expectedStatus);
+
+        // unregister receiver
+        receiver.unregisterQuietly();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillValueTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillValueTest.java
new file mode 100644
index 0000000..b2fdb0c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillValueTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.view.autofill.AutofillValue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@AppModeFull(reason = "Unit test")
+@RunWith(AndroidJUnit4.class)
+public class AutofillValueTest {
+
+    @Test
+    public void createTextValue() throws Exception {
+        assertThat(AutofillValue.forText(null)).isNull();
+
+        assertThat(AutofillValue.forText("").isText()).isTrue();
+        assertThat(AutofillValue.forText("").isToggle()).isFalse();
+        assertThat(AutofillValue.forText("").isList()).isFalse();
+        assertThat(AutofillValue.forText("").isDate()).isFalse();
+
+        AutofillValue emptyV = AutofillValue.forText("");
+        assertThat(emptyV.getTextValue().toString()).isEqualTo("");
+
+        final AutofillValue v = AutofillValue.forText("someText");
+        assertThat(v.getTextValue()).isEqualTo("someText");
+
+        assertThrows(IllegalStateException.class, v::getToggleValue);
+        assertThrows(IllegalStateException.class, v::getListValue);
+        assertThrows(IllegalStateException.class, v::getDateValue);
+    }
+
+    @Test
+    public void createToggleValue() throws Exception {
+        assertThat(AutofillValue.forToggle(true).getToggleValue()).isTrue();
+        assertThat(AutofillValue.forToggle(false).getToggleValue()).isFalse();
+
+        assertThat(AutofillValue.forToggle(true).isText()).isFalse();
+        assertThat(AutofillValue.forToggle(true).isToggle()).isTrue();
+        assertThat(AutofillValue.forToggle(true).isList()).isFalse();
+        assertThat(AutofillValue.forToggle(true).isDate()).isFalse();
+
+
+        final AutofillValue v = AutofillValue.forToggle(true);
+
+        assertThrows(IllegalStateException.class, v::getTextValue);
+        assertThrows(IllegalStateException.class, v::getListValue);
+        assertThrows(IllegalStateException.class, v::getDateValue);
+    }
+
+    @Test
+    public void createListValue() throws Exception {
+        assertThat(AutofillValue.forList(-1).getListValue()).isEqualTo(-1);
+        assertThat(AutofillValue.forList(0).getListValue()).isEqualTo(0);
+        assertThat(AutofillValue.forList(1).getListValue()).isEqualTo(1);
+
+        assertThat(AutofillValue.forList(0).isText()).isFalse();
+        assertThat(AutofillValue.forList(0).isToggle()).isFalse();
+        assertThat(AutofillValue.forList(0).isList()).isTrue();
+        assertThat(AutofillValue.forList(0).isDate()).isFalse();
+
+        final AutofillValue v = AutofillValue.forList(0);
+
+        assertThrows(IllegalStateException.class, v::getTextValue);
+        assertThrows(IllegalStateException.class, v::getToggleValue);
+        assertThrows(IllegalStateException.class, v::getDateValue);
+    }
+
+    @Test
+    public void createDateValue() throws Exception {
+        assertThat(AutofillValue.forDate(-1).getDateValue()).isEqualTo(-1);
+        assertThat(AutofillValue.forDate(0).getDateValue()).isEqualTo(0);
+        assertThat(AutofillValue.forDate(1).getDateValue()).isEqualTo(1);
+
+        assertThat(AutofillValue.forDate(0).isText()).isFalse();
+        assertThat(AutofillValue.forDate(0).isToggle()).isFalse();
+        assertThat(AutofillValue.forDate(0).isList()).isFalse();
+        assertThat(AutofillValue.forDate(0).isDate()).isTrue();
+
+        final AutofillValue v = AutofillValue.forDate(0);
+
+        assertThrows(IllegalStateException.class, v::getTextValue);
+        assertThrows(IllegalStateException.class, v::getToggleValue);
+        assertThrows(IllegalStateException.class, v::getListValue);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/BatchUpdatesTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/BatchUpdatesTest.java
new file mode 100644
index 0000000..b77574a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/BatchUpdatesTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.InternalTransformation;
+import android.service.autofill.Transformation;
+import android.widget.RemoteViews;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class BatchUpdatesTest {
+
+    private final BatchUpdates.Builder mBuilder = new BatchUpdates.Builder();
+
+    @Test
+    public void testAddTransformation_null() {
+        assertThrows(IllegalArgumentException.class, () ->  mBuilder.transformChild(42, null));
+    }
+
+    @Test
+    public void testAddTransformation_invalidClass() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.transformChild(42, mock(Transformation.class)));
+    }
+
+    @Test
+    public void testSetUpdateTemplate_null() {
+        assertThrows(NullPointerException.class, () ->  mBuilder.updateTemplate(null));
+    }
+
+    @Test
+    public void testEmptyObject() {
+        assertThrows(IllegalStateException.class, () ->  mBuilder.build());
+    }
+
+    @Test
+    public void testNoMoreChangesAfterBuild() {
+        assertThat(mBuilder.updateTemplate(mock(RemoteViews.class)).build()).isNotNull();
+        assertThrows(IllegalStateException.class,
+                () ->  mBuilder.updateTemplate(mock(RemoteViews.class)));
+        assertThrows(IllegalStateException.class,
+                () ->  mBuilder.transformChild(42, mock(InternalTransformation.class)));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceMatcher.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceMatcher.java
new file mode 100644
index 0000000..390eecd
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceMatcher.java
@@ -0,0 +1,36 @@
+/*
+ * 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.autofillservice.cts.unittests;
+
+import org.mockito.ArgumentMatcher;
+
+final class CharSequenceMatcher implements ArgumentMatcher<CharSequence> {
+    private final CharSequence mExpected;
+
+    CharSequenceMatcher(CharSequence expected) {
+        mExpected = expected;
+    }
+
+    @Override
+    public boolean matches(CharSequence actual) {
+        return actual.toString().equals(mExpected.toString());
+    }
+
+    @Override
+    public String toString() {
+        return mExpected.toString();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceTransformationTest.java
new file mode 100644
index 0000000..8fe5af8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceTransformationTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.ValueFinder;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class CharSequenceTransformationTest {
+
+    @Test
+    public void testAllNullBuilder() {
+        assertThrows(NullPointerException.class,
+                () ->  new CharSequenceTransformation.Builder(null, null, null));
+    }
+
+    @Test
+    public void testNullAutofillIdBuilder() {
+        assertThrows(NullPointerException.class,
+                () -> new CharSequenceTransformation.Builder(null, Pattern.compile(""), ""));
+    }
+
+    @Test
+    public void testNullRegexBuilder() {
+        assertThrows(NullPointerException.class,
+                () -> new CharSequenceTransformation.Builder(new AutofillId(1), null, ""));
+    }
+
+    @Test
+    public void testNullSubstBuilder() {
+        assertThrows(NullPointerException.class,
+                () -> new CharSequenceTransformation.Builder(new AutofillId(1), Pattern.compile(""),
+                        null));
+    }
+
+    @Test
+    public void testBadSubst() {
+        AutofillId id1 = new AutofillId(1);
+        AutofillId id2 = new AutofillId(2);
+        AutofillId id3 = new AutofillId(3);
+        AutofillId id4 = new AutofillId(4);
+
+        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
+                Pattern.compile("(.)"), "1=$1");
+
+        // bad subst: The regex has no capture groups
+        b.addField(id2, Pattern.compile("."), "2=$1");
+
+        // bad subst: The regex does not have enough capture groups
+        b.addField(id3, Pattern.compile("(.)"), "3=$2");
+
+        b.addField(id4, Pattern.compile("(.)"), "4=$1");
+
+        CharSequenceTransformation trans = b.build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("a");
+        when(finder.findByAutofillId(id2)).thenReturn("b");
+        when(finder.findByAutofillId(id3)).thenReturn("c");
+        when(finder.findByAutofillId(id4)).thenReturn("d");
+
+        assertThrows(IndexOutOfBoundsException.class, () -> trans.apply(finder, template, 0));
+
+        // fail one, fail all
+        verify(template, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testUnknownField() throws Exception {
+        AutofillId id1 = new AutofillId(1);
+        AutofillId id2 = new AutofillId(2);
+        AutofillId unknownId = new AutofillId(42);
+
+        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
+                Pattern.compile(".*"), "1");
+
+        // bad subst: The field will not be found
+        b.addField(unknownId, Pattern.compile(".*"), "unknown");
+
+        b.addField(id2, Pattern.compile(".*"), "2");
+
+        CharSequenceTransformation trans = b.build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("1");
+        when(finder.findByAutofillId(id2)).thenReturn("2");
+        when(finder.findByAutofillId(unknownId)).thenReturn(null);
+
+        trans.apply(finder, template, 0);
+
+        // if a view cannot be found, nothing is not, not even partial results
+        verify(template, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testCreditCardObfuscator() throws Exception {
+        AutofillId creditCardFieldId = new AutofillId(1);
+        CharSequenceTransformation trans = new CharSequenceTransformation.Builder(creditCardFieldId,
+                Pattern.compile("^\\s*\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?(\\d{4})\\s*$"),
+                "...$1").build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("1234 5678 9012 3456");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("...3456")));
+    }
+
+    @Test
+    public void testReplaceAllByOne() throws Exception {
+        AutofillId id = new AutofillId(1);
+        CharSequenceTransformation trans = new CharSequenceTransformation
+                .Builder(id, Pattern.compile("."), "*")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("four");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("****")));
+    }
+
+    @Test
+    public void testPartialMatchIsIgnored() throws Exception {
+        AutofillId id = new AutofillId(1);
+        CharSequenceTransformation trans = new CharSequenceTransformation
+                .Builder(id, Pattern.compile("^MATCH$"), "*")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("preMATCHpost");
+
+        trans.apply(finder, template, 0);
+
+        verify(template, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void userNameObfuscator() throws Exception {
+        AutofillId userNameFieldId = new AutofillId(1);
+        AutofillId passwordFieldId = new AutofillId(2);
+        CharSequenceTransformation trans = new CharSequenceTransformation
+                .Builder(userNameFieldId, Pattern.compile("(.*)"), "$1")
+                .addField(passwordFieldId, Pattern.compile(".*(..)$"), "/..$1")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(userNameFieldId)).thenReturn("myUserName");
+        when(finder.findByAutofillId(passwordFieldId)).thenReturn("myPassword");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setCharSequence(eq(0), any(),
+                argThat(new CharSequenceMatcher("myUserName/..rd")));
+    }
+
+    @Test
+    public void testMismatch() throws Exception {
+        AutofillId id1 = new AutofillId(1);
+        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
+                Pattern.compile("Who are you?"), "1");
+
+        CharSequenceTransformation trans = b.build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("I'm Batman!");
+
+        trans.apply(finder, template, 0);
+
+        // If the match fails, the view should not change.
+        verify(template, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testFieldsAreAppliedInOrder() throws Exception {
+        AutofillId id1 = new AutofillId(1);
+        AutofillId id2 = new AutofillId(2);
+        AutofillId id3 = new AutofillId(3);
+        CharSequenceTransformation trans = new CharSequenceTransformation
+                .Builder(id1, Pattern.compile("a"), "A")
+                .addField(id3, Pattern.compile("c"), "C")
+                .addField(id2, Pattern.compile("b"), "B")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("a");
+        when(finder.findByAutofillId(id2)).thenReturn("b");
+        when(finder.findByAutofillId(id3)).thenReturn("c");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("ACB")));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/CompositeUserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/CompositeUserDataTest.java
new file mode 100644
index 0000000..908bf21
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/CompositeUserDataTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CompositeUserData;
+import android.service.autofill.UserData;
+import android.util.ArrayMap;
+
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class CompositeUserDataTest {
+
+    private final String mShortValue = Strings.repeat("k", UserData.getMinValueLength() - 1);
+    private final String mLongValue = "LONG VALUE, Y U NO SHORTER"
+            + Strings.repeat("?", UserData.getMaxValueLength());
+    private final String mId = "4815162342";
+    private final String mId2 = "4815162343";
+    private final String mCategoryId = "id1";
+    private final String mCategoryId2 = "id2";
+    private final String mCategoryId3 = "id3";
+    private final String mValue = mShortValue + "-1";
+    private final String mValue2 = mShortValue + "-2";
+    private final String mValue3 = mShortValue + "-3";
+    private final String mValue4 = mShortValue + "-4";
+    private final String mValue5 = mShortValue + "-5";
+    private final String mAlgo = "algo";
+    private final String mAlgo2 = "algo2";
+    private final String mAlgo3 = "algo3";
+    private final String mAlgo4 = "algo4";
+
+    private final UserData mEmptyGenericUserData = new UserData.Builder(mId, mValue, mCategoryId)
+            .build();
+    private final UserData mLoadedGenericUserData = new UserData.Builder(mId, mValue, mCategoryId)
+            .add(mValue2, mCategoryId2)
+            .setFieldClassificationAlgorithm(mAlgo, createBundle(false))
+            .setFieldClassificationAlgorithmForCategory(mCategoryId2, mAlgo2, createBundle(false))
+            .build();
+    private final UserData mEmptyPackageUserData = new UserData.Builder(mId2, mValue3, mCategoryId3)
+            .build();
+    private final UserData mLoadedPackageUserData = new UserData
+            .Builder(mId2, mValue3, mCategoryId3)
+            .add(mValue4, mCategoryId2)
+            .setFieldClassificationAlgorithm(mAlgo3, createBundle(true))
+            .setFieldClassificationAlgorithmForCategory(mCategoryId2, mAlgo4, createBundle(true))
+            .build();
+
+
+    @Test
+    public void testMergeInvalid_bothNull() {
+        assertThrows(NullPointerException.class, () -> new CompositeUserData(null, null));
+    }
+
+    @Test
+    public void testMergeInvalid_nullPackageUserData() {
+        assertThrows(NullPointerException.class,
+                () -> new CompositeUserData(mEmptyGenericUserData, null));
+    }
+
+    @Test
+    public void testMerge_nullGenericUserData() {
+        final CompositeUserData userData = new CompositeUserData(null, mEmptyPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(1);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(1);
+        assertThat(values[0]).isEqualTo(mValue3);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
+        assertThat(userData.getDefaultFieldClassificationArgs()).isNull();
+    }
+
+    @Test
+    public void testMerge_bothEmpty() {
+        final CompositeUserData userData = new CompositeUserData(mEmptyGenericUserData,
+                mEmptyPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(2);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(2);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
+        assertThat(userData.getDefaultFieldClassificationArgs()).isNull();
+    }
+
+    @Test
+    public void testMerge_emptyGenericUserData() {
+        final CompositeUserData userData = new CompositeUserData(mEmptyGenericUserData,
+                mLoadedPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(3);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId2);
+        assertThat(categoryIds[2]).isEqualTo(mCategoryId);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(3);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue4);
+        assertThat(values[2]).isEqualTo(mValue);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo3);
+
+        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
+        assertThat(defaultArgs).isNotNull();
+        assertThat(defaultArgs.getBoolean("isPackage")).isTrue();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo4);
+
+        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
+        assertThat(args.size()).isEqualTo(1);
+        assertThat(args.containsKey(mCategoryId2)).isTrue();
+        assertThat(args.get(mCategoryId2)).isNotNull();
+        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isTrue();
+    }
+
+    @Test
+    public void testMerge_emptyPackageUserData() {
+        final CompositeUserData userData = new CompositeUserData(mLoadedGenericUserData,
+                mEmptyPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(3);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId);
+        assertThat(categoryIds[2]).isEqualTo(mCategoryId2);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(3);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue);
+        assertThat(values[2]).isEqualTo(mValue2);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo);
+
+        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
+        assertThat(defaultArgs).isNotNull();
+        assertThat(defaultArgs.getBoolean("isPackage")).isFalse();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo2);
+
+        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
+        assertThat(args.size()).isEqualTo(1);
+        assertThat(args.containsKey(mCategoryId2)).isTrue();
+        assertThat(args.get(mCategoryId2)).isNotNull();
+        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isFalse();
+    }
+
+
+    @Test
+    public void testMerge_bothHaveData() {
+        final CompositeUserData userData = new CompositeUserData(mLoadedGenericUserData,
+                mLoadedPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(3);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId2);
+        assertThat(categoryIds[2]).isEqualTo(mCategoryId);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(3);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue4);
+        assertThat(values[2]).isEqualTo(mValue);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo3);
+        assertThat(userData.getDefaultFieldClassificationArgs()).isNotNull();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo4);
+
+        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
+        assertThat(defaultArgs).isNotNull();
+        assertThat(defaultArgs.getBoolean("isPackage")).isTrue();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo4);
+
+        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
+        assertThat(args.size()).isEqualTo(1);
+        assertThat(args.containsKey(mCategoryId2)).isTrue();
+        assertThat(args.get(mCategoryId2)).isNotNull();
+        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isTrue();
+    }
+
+    private Bundle createBundle(Boolean isPackageBundle) {
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean("isPackage", isPackageBundle);
+        return bundle;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/CustomDescriptionUnitTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/CustomDescriptionUnitTest.java
new file mode 100644
index 0000000..9bdd32b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/CustomDescriptionUnitTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.InternalOnClickAction;
+import android.service.autofill.InternalTransformation;
+import android.service.autofill.InternalValidator;
+import android.service.autofill.OnClickAction;
+import android.service.autofill.Transformation;
+import android.service.autofill.Validator;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class CustomDescriptionUnitTest {
+
+    private final CustomDescription.Builder mBuilder =
+            new CustomDescription.Builder(mock(RemoteViews.class));
+    private final BatchUpdates mValidUpdate =
+            new BatchUpdates.Builder().updateTemplate(mock(RemoteViews.class)).build();
+    private final Transformation mValidTransformation = mock(InternalTransformation.class);
+    private final Validator mValidCondition = mock(InternalValidator.class);
+    private final OnClickAction mValidAction = mock(InternalOnClickAction.class);
+
+    @Test
+    public void testNullConstructor() {
+        assertThrows(NullPointerException.class, () ->  new CustomDescription.Builder(null));
+    }
+
+    @Test
+    public void testAddChild_null() {
+        assertThrows(IllegalArgumentException.class, () ->  mBuilder.addChild(42, null));
+    }
+
+    @Test
+    public void testAddChild_invalidImplementation() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.addChild(42, mock(Transformation.class)));
+    }
+
+    @Test
+    public void testBatchUpdate_nullCondition() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.batchUpdate(null, mValidUpdate));
+    }
+
+    @Test
+    public void testBatchUpdate_invalidImplementation() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.batchUpdate(mock(Validator.class), mValidUpdate));
+    }
+
+    @Test
+    public void testBatchUpdate_nullUpdates() {
+        assertThrows(NullPointerException.class,
+                () ->  mBuilder.batchUpdate(mValidCondition, null));
+    }
+
+    @Test
+    public void testSetOnClickAction_null() {
+        assertThrows(IllegalArgumentException.class, () ->  mBuilder.addOnClickAction(42, null));
+    }
+
+    @Test
+    public void testSetOnClickAction_invalidImplementation() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.addOnClickAction(42, mock(OnClickAction.class)));
+    }
+
+    @Test
+    public void testSetOnClickAction_thereCanBeOnlyOne() {
+        final CustomDescription customDescription = mBuilder
+                .addOnClickAction(42, mock(InternalOnClickAction.class))
+                .addOnClickAction(42, mValidAction)
+                .build();
+        final SparseArray<InternalOnClickAction> actions = customDescription.getActions();
+        assertThat(actions.size()).isEqualTo(1);
+        assertThat(actions.keyAt(0)).isEqualTo(42);
+        assertThat(actions.valueAt(0)).isSameInstanceAs(mValidAction);
+    }
+
+    @Test
+    public void testBuild_valid() {
+        new CustomDescription.Builder(mock(RemoteViews.class)).build();
+        new CustomDescription.Builder(mock(RemoteViews.class))
+            .addChild(108, mValidTransformation)
+            .batchUpdate(mValidCondition, mValidUpdate)
+            .addOnClickAction(42, mValidAction)
+            .build();
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        mBuilder.build();
+
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.addChild(108, mValidTransformation));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.batchUpdate(mValidCondition, mValidUpdate));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.addOnClickAction(42, mValidAction));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java
new file mode 100644
index 0000000..703c3ab
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceSpec;
+import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.Dataset;
+import android.service.autofill.InlinePresentation;
+import android.util.Size;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+import android.widget.inline.InlinePresentationSpec;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class DatasetTest {
+
+    private final AutofillId mId = new AutofillId(42);
+    private final AutofillValue mValue = AutofillValue.forText("ValuableLikeGold");
+    private final Pattern mFilter = Pattern.compile("whatever");
+    private final InlinePresentation mInlinePresentation = new InlinePresentation(
+            new Slice.Builder(new Uri.Builder().appendPath("DatasetTest").build(),
+                    new SliceSpec("DatasetTest", 1)).build(),
+            new InlinePresentationSpec.Builder(new Size(10, 10),
+                    new Size(50, 50)).build(), /* pinned= */ false);
+
+    private final RemoteViews mPresentation = mock(RemoteViews.class);
+
+    @Test
+    public void testBuilder_nullPresentation() {
+        assertThrows(NullPointerException.class, () -> new Dataset.Builder((RemoteViews) null));
+    }
+
+    @Test
+    public void testBuilder_nullInlinePresentation() {
+        assertThrows(NullPointerException.class,
+                () -> new Dataset.Builder((InlinePresentation) null));
+    }
+
+    @Test
+    public void testBuilder_validPresentations() {
+        assertThat(new Dataset.Builder(mPresentation)).isNotNull();
+        assertThat(new Dataset.Builder(mInlinePresentation)).isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setNullInlinePresentation() {
+        final Dataset.Builder builder = new Dataset.Builder(mPresentation);
+        assertThrows(NullPointerException.class, () -> builder.setInlinePresentation(null));
+    }
+
+    @Test
+    public void testBuilder_setInlinePresentation() {
+        assertThat(new Dataset.Builder().setInlinePresentation(mInlinePresentation)).isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setValueNullId() {
+        final Dataset.Builder builder = new Dataset.Builder(mPresentation);
+        assertThrows(NullPointerException.class, () -> builder.setValue(null, mValue));
+    }
+
+    @Test
+    public void testBuilder_setValueWithoutPresentation() {
+        // Just assert that it builds without throwing an exception.
+        assertThat(new Dataset.Builder().setValue(mId, mValue).build()).isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setValueWithNullPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+                (RemoteViews) null));
+    }
+
+    @Test
+    public void testBuilder_setValueWithBothPresentation_nullPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+                null, mInlinePresentation));
+    }
+
+    @Test
+    public void testBuilder_setValueWithBothPresentation_nullInlinePresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+                mPresentation, null));
+    }
+
+    @Test
+    public void testBuilder_setValueWithBothPresentation_bothNull() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+                (RemoteViews) null, null));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithNullFilter() {
+        assertThat(new Dataset.Builder(mPresentation).setValue(mId, mValue, (Pattern) null).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithPresentation_nullFilter() {
+        assertThat(new Dataset.Builder().setValue(mId, mValue, null, mPresentation).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithPresentation_nullPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
+                null));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithoutPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue, mFilter));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithBothPresentation_nullPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
+                null, mInlinePresentation));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithBothPresentation_nullInlinePresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
+                mPresentation, null));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithBothPresentation_bothNull() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
+                null, null));
+    }
+
+    @Test
+    public void testBuilder_setFieldInlinePresentations() {
+        assertThat(new Dataset.Builder().setFieldInlinePresentation(mId, mValue, mFilter,
+                mInlinePresentation)).isNotNull();
+    }
+
+    @Test
+    public void testBuild_noValues() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(IllegalStateException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        builder.setValue(mId, mValue, mPresentation);
+        assertThat(builder.build()).isNotNull();
+        assertThrows(IllegalStateException.class, () -> builder.build());
+        assertThrows(IllegalStateException.class,
+                () -> builder.setInlinePresentation(mInlinePresentation));
+        assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mPresentation));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mFilter));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mFilter, mPresentation));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mPresentation, mInlinePresentation));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mFilter, mPresentation, mInlinePresentation));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setFieldInlinePresentation(mId, mValue, mFilter,
+                        mInlinePresentation));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/DateTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/DateTransformationTest.java
new file mode 100644
index 0000000..8e37469
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/DateTransformationTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.autofillservice.cts.unittests;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.icu.text.SimpleDateFormat;
+import android.icu.util.Calendar;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.DateTransformation;
+import android.service.autofill.ValueFinder;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class DateTransformationTest {
+
+    @Mock private ValueFinder mValueFinder;
+    @Mock private RemoteViews mTemplate;
+
+    private final AutofillId mFieldId = new AutofillId(42);
+
+    @Test
+    public void testConstructor_nullFieldId() {
+        assertThrows(NullPointerException.class,
+                () -> new DateTransformation(null, new SimpleDateFormat()));
+    }
+
+    @Test
+    public void testConstructor_nullDateFormat() {
+        assertThrows(NullPointerException.class, () -> new DateTransformation(mFieldId, null));
+    }
+
+    @Test
+    public void testFieldNotFound() throws Exception {
+        final DateTransformation trans = new DateTransformation(mFieldId, new SimpleDateFormat());
+
+        trans.apply(mValueFinder, mTemplate, 0);
+
+        verify(mTemplate, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testInvalidAutofillValueType() throws Exception {
+        final DateTransformation trans = new DateTransformation(mFieldId, new SimpleDateFormat());
+
+        when(mValueFinder.findRawValueByAutofillId(mFieldId))
+                .thenReturn(AutofillValue.forText("D'OH"));
+        trans.apply(mValueFinder, mTemplate, 0);
+
+        verify(mTemplate, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testValidAutofillValue() throws Exception {
+        final DateTransformation trans = new DateTransformation(mFieldId,
+                new SimpleDateFormat("MM/yyyy"));
+
+        final Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.YEAR, 2012);
+        cal.set(Calendar.MONTH, Calendar.DECEMBER);
+        cal.set(Calendar.DAY_OF_MONTH, 20);
+
+        when(mValueFinder.findRawValueByAutofillId(mFieldId))
+                .thenReturn(AutofillValue.forDate(cal.getTimeInMillis()));
+
+        trans.apply(mValueFinder, mTemplate, 0);
+
+        verify(mTemplate).setCharSequence(eq(0), any(),
+                argThat(new CharSequenceMatcher("12/2012")));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/DateValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/DateValueSanitizerTest.java
new file mode 100644
index 0000000..de2f083
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/DateValueSanitizerTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.icu.text.SimpleDateFormat;
+import android.icu.util.Calendar;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.DateValueSanitizer;
+import android.util.Log;
+import android.view.autofill.AutofillValue;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Date;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class DateValueSanitizerTest {
+
+    private static final String TAG = "DateValueSanitizerTest";
+
+    private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MM/yyyy");
+
+    @Test
+    public void testConstructor_nullDateFormat() {
+        assertThrows(NullPointerException.class, () -> new DateValueSanitizer(null));
+    }
+
+    @Test
+    public void testSanitize_nullValue() throws Exception {
+        final DateValueSanitizer sanitizer = new DateValueSanitizer(new SimpleDateFormat());
+        assertThat(sanitizer.sanitize(null)).isNull();
+    }
+
+    @Test
+    public void testSanitize_invalidValue() throws Exception {
+        final DateValueSanitizer sanitizer = new DateValueSanitizer(new SimpleDateFormat());
+        assertThat(sanitizer.sanitize(AutofillValue.forText("D'OH!"))).isNull();
+    }
+
+    @Test
+    public void testSanitize_ok() throws Exception {
+        final Calendar inputCal = Calendar.getInstance();
+        inputCal.set(Calendar.YEAR, 2012);
+        inputCal.set(Calendar.MONTH, Calendar.DECEMBER);
+        inputCal.set(Calendar.DAY_OF_MONTH, 20);
+        final long inputDate = inputCal.getTimeInMillis();
+        final AutofillValue inputValue = AutofillValue.forDate(inputDate);
+        Log.v(TAG, "Input date: " + inputDate + " >> " + new Date(inputDate));
+
+        final Calendar expectedCal = Calendar.getInstance();
+        expectedCal.clear(); // We just care for year and month...
+        expectedCal.set(Calendar.YEAR, 2012);
+        expectedCal.set(Calendar.MONTH, Calendar.DECEMBER);
+        final long expectedDate = expectedCal.getTimeInMillis();
+        final AutofillValue expectedValue = AutofillValue.forDate(expectedDate);
+        Log.v(TAG, "Exected date: " + expectedDate + " >> " + new Date(expectedDate));
+
+        final DateValueSanitizer sanitizer = new DateValueSanitizer(
+                mDateFormat);
+        final AutofillValue sanitizedValue = sanitizer.sanitize(inputValue);
+        final long sanitizedDate = sanitizedValue.getDateValue();
+        Log.v(TAG, "Sanitized date: " + sanitizedDate + " >> " + new Date(sanitizedDate));
+        assertThat(sanitizedDate).isEqualTo(expectedDate);
+        assertThat(sanitizedValue).isEqualTo(expectedValue);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/FillResponseTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/FillResponseTest.java
new file mode 100644
index 0000000..9c1e75b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/FillResponseTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static android.service.autofill.FillResponse.FLAG_DISABLE_ACTIVITY_ONLY;
+import static android.service.autofill.FillResponse.FLAG_TRACK_CONTEXT_COMMITED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.UserData;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class FillResponseTest {
+
+    private final AutofillId mAutofillId = new AutofillId(42);
+    private final FillResponse.Builder mBuilder = new FillResponse.Builder();
+    private final AutofillId[] mIds = new AutofillId[] { mAutofillId };
+    private final SaveInfo mSaveInfo = new SaveInfo.Builder(0, mIds).build();
+    private final Bundle mClientState = new Bundle();
+    private final Dataset mDataset = new Dataset.Builder()
+            .setValue(mAutofillId, AutofillValue.forText("forty-two"))
+            .build();
+    private final long mDisableDuration = 666;
+    @Mock private RemoteViews mPresentation;
+    @Mock private RemoteViews mHeader;
+    @Mock private RemoteViews mFooter;
+    @Mock private IntentSender mIntentSender;
+    private final UserData mUserData = new UserData.Builder("id", "value", "cat").build();
+
+    @Test
+    public void testBuilder_setAuthentication_invalid() {
+        // null ids
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(null, mIntentSender, mPresentation));
+        // empty ids
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(new AutofillId[] {}, mIntentSender,
+                        mPresentation));
+        // ids with null value
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(new AutofillId[] {null}, mIntentSender,
+                        mPresentation));
+        // null intent sender
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(mIds, null, mPresentation));
+        // null presentation
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(mIds, mIntentSender, null));
+    }
+
+    @Test
+    public void testBuilder_setAuthentication_valid() {
+        new FillResponse.Builder().setAuthentication(mIds, null, null);
+        new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+    }
+
+    @Test
+    public void testBuilder_setAuthentication_illegalState() {
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setHeader(mHeader).setAuthentication(mIds,
+                        mIntentSender, mPresentation));
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setFooter(mFooter).setAuthentication(mIds,
+                        mIntentSender, mPresentation));
+    }
+
+    @Test
+    public void testBuilder_setHeaderOrFooterInvalid() {
+        assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setHeader(null));
+        assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setFooter(null));
+    }
+
+    @Test
+    public void testBuilder_setHeaderOrFooterAfterAuthentication() {
+        FillResponse.Builder builder =
+                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+        assertThrows(IllegalStateException.class, () -> builder.setHeader(mHeader));
+        assertThrows(IllegalStateException.class, () -> builder.setHeader(mFooter));
+    }
+
+    @Test
+    public void testBuilder_setUserDataInvalid() {
+        assertThrows(NullPointerException.class, () -> new FillResponse.Builder()
+                .setUserData(null));
+    }
+
+    @Test
+    public void testBuilder_setUserDataAfterAuthentication() {
+        FillResponse.Builder builder =
+                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+        assertThrows(IllegalStateException.class, () -> builder.setUserData(mUserData));
+    }
+
+    @Test
+    public void testBuilder_setFlag_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.setFlags(-1));
+    }
+
+    @Test
+    public void testBuilder_setFlag_valid() {
+        mBuilder.setFlags(0);
+        mBuilder.setFlags(FLAG_TRACK_CONTEXT_COMMITED);
+        mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY);
+    }
+
+    @Test
+    public void testBuilder_disableAutofill_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(0));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(-1));
+    }
+
+    @Test
+    public void testBuilder_disableAutofill_valid() {
+        mBuilder.disableAutofill(mDisableDuration);
+        mBuilder.disableAutofill(Long.MAX_VALUE);
+    }
+
+    @Test
+    public void testBuilder_disableAutofill_mustBeTheOnlyMethodCalled() {
+        // No method can be called after disableAutofill()
+        mBuilder.disableAutofill(mDisableDuration);
+        assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
+        assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(mDataset));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setFieldClassificationIds(mAutofillId));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setClientState(mClientState));
+
+        // And vice-versa...
+        final FillResponse.Builder builder1 = new FillResponse.Builder().setSaveInfo(mSaveInfo);
+        assertThrows(IllegalStateException.class, () -> builder1.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder2 = new FillResponse.Builder().addDataset(mDataset);
+        assertThrows(IllegalStateException.class, () -> builder2.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder3 =
+                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+        assertThrows(IllegalStateException.class, () -> builder3.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder4 =
+                new FillResponse.Builder().setFieldClassificationIds(mAutofillId);
+        assertThrows(IllegalStateException.class, () -> builder4.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder5 =
+                new FillResponse.Builder().setClientState(mClientState);
+        assertThrows(IllegalStateException.class, () -> builder5.disableAutofill(mDisableDuration));
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_invalid() {
+        assertThrows(NullPointerException.class,
+                () -> mBuilder.setFieldClassificationIds((AutofillId) null));
+        assertThrows(NullPointerException.class,
+                () -> mBuilder.setFieldClassificationIds((AutofillId[]) null));
+        final AutofillId[] oneTooMany =
+                new AutofillId[UserData.getMaxFieldClassificationIdsSize() + 1];
+        for (int i = 0; i < oneTooMany.length; i++) {
+            oneTooMany[i] = new AutofillId(i);
+        }
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setFieldClassificationIds(oneTooMany));
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_valid() {
+        mBuilder.setFieldClassificationIds(mAutofillId);
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_setsFlag() {
+        mBuilder.setFieldClassificationIds(mAutofillId);
+        assertThat(mBuilder.build().getFlags()).isEqualTo(FLAG_TRACK_CONTEXT_COMMITED);
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_addsFlag() {
+        mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY).setFieldClassificationIds(mAutofillId);
+        assertThat(mBuilder.build().getFlags())
+                .isEqualTo(FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+    }
+
+    @Test
+    public void testBuild_invalid() {
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+    }
+
+    @Test
+    public void testBuild_valid() {
+        // authentication only
+        assertThat(new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation)
+                .build()).isNotNull();
+        // save info only
+        assertThat(new FillResponse.Builder().setSaveInfo(mSaveInfo).build()).isNotNull();
+        // dataset only
+        assertThat(new FillResponse.Builder().addDataset(mDataset).build()).isNotNull();
+        // disable autofill only
+        assertThat(new FillResponse.Builder().disableAutofill(mDisableDuration).build())
+                .isNotNull();
+        // fill detection only
+        assertThat(new FillResponse.Builder().setFieldClassificationIds(mAutofillId).build())
+                .isNotNull();
+        // client state only
+        assertThat(new FillResponse.Builder().setClientState(mClientState).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testBuilder_build_headerOrFooterWithoutDatasets() {
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setHeader(mHeader).build());
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setFooter(mFooter).build());
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        assertThat(mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build())
+                .isNotNull();
+
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build());
+        assertThrows(IllegalStateException.class, () -> mBuilder.setIgnoredIds(mIds));
+        assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(null));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setClientState(mClientState));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setFlags(0));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setFieldClassificationIds(mAutofillId));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setHeader(mHeader));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setFooter(mFooter));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setUserData(mUserData));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setPresentationCancelIds(null));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/ImageTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/ImageTransformationTest.java
new file mode 100644
index 0000000..ea7fc63
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/ImageTransformationTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.ImageTransformation;
+import android.service.autofill.ValueFinder;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class ImageTransformationTest {
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testAllNullBuilder() {
+        assertThrows(NullPointerException.class,
+                () ->  new ImageTransformation.Builder(null, null, 0));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testNullAutofillIdBuilder() {
+        assertThrows(NullPointerException.class,
+                () ->  new ImageTransformation.Builder(null, Pattern.compile(""), 1));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testNullRegexBuilder() {
+        assertThrows(NullPointerException.class,
+                () ->  new ImageTransformation.Builder(new AutofillId(1), null, 1));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testNullSubstBuilder() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  new ImageTransformation.Builder(new AutofillId(1), Pattern.compile(""), 0));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void fieldCannotBeFound() throws Exception {
+        AutofillId unknownId = new AutofillId(42);
+
+        ImageTransformation trans = new ImageTransformation
+                .Builder(unknownId, Pattern.compile("val"), 1)
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(unknownId)).thenReturn(null);
+
+        trans.apply(finder, template, 0);
+
+        // if a view cannot be found, nothing is set
+        verify(template, never()).setImageViewResource(anyInt(), anyInt());
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void theOneOptionsMatches() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*"), 42)
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("val");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setImageViewResource(0, 42);
+    }
+
+    @Test
+    public void theOneOptionsMatchesWithContentDescription() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*"), 42, "Are you content?")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("val");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setImageViewResource(0, 42);
+        verify(template).setContentDescription(0, "Are you content?");
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void noOptionsMatches() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile("val"), 42)
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("bad-val");
+
+        trans.apply(finder, template, 0);
+
+        verify(template, never()).setImageViewResource(anyInt(), anyInt());
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void multipleOptionsOneMatches() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*1"), 1)
+                .addOption(Pattern.compile(".*2"), 2)
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("val-2");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setImageViewResource(0, 2);
+    }
+
+    @Test
+    public void multipleOptionsOneMatchesWithContentDescription() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*1"), 1, "Are you content?")
+                .addOption(Pattern.compile(".*2"), 2, "I am content")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("val-2");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setImageViewResource(0, 2);
+        verify(template).setContentDescription(0, "I am content");
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void twoOptionsMatch() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*a.*"), 1)
+                .addOption(Pattern.compile(".*b.*"), 2)
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("ab");
+
+        trans.apply(finder, template, 0);
+
+        // If two options match, the first one is picked
+        verify(template, only()).setImageViewResource(0, 1);
+    }
+
+    @Test
+    public void twoOptionsMatchWithContentDescription() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*a.*"), 1, "Are you content?")
+                .addOption(Pattern.compile(".*b.*"), 2, "No, I'm not")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("ab");
+
+        trans.apply(finder, template, 0);
+
+        // If two options match, the first one is picked
+        verify(template).setImageViewResource(0, 1);
+        verify(template).setContentDescription(0, "Are you content?");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/InlinePresentationTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/InlinePresentationTest.java
new file mode 100644
index 0000000..0bf230c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/InlinePresentationTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceSpec;
+import android.net.Uri;
+import android.os.Parcel;
+import android.service.autofill.InlinePresentation;
+import android.util.Size;
+import android.widget.inline.InlinePresentationSpec;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InlinePresentationTest {
+
+    @Test
+    public void testNullInlinePresentationSpecsThrowsException() {
+        assertThrows(NullPointerException.class,
+                () -> createInlinePresentation(/* createSlice */true, /* createSpec */  false));
+    }
+
+    @Test
+    public void testNullSliceThrowsException() {
+        assertThrows(NullPointerException.class,
+                () -> createInlinePresentation(/* createSlice */false, /* createSpec */  true));
+    }
+
+    @Test
+    public void testInlinePresentationValues() {
+        InlinePresentation presentation =
+                createInlinePresentation(/* createSlice */true, /* createSpec */  true);
+
+        assertThat(presentation.isPinned()).isFalse();
+        assertThat(presentation.getInlinePresentationSpec()).isNotNull();
+        assertThat(presentation.getSlice()).isNotNull();
+        assertThat(presentation.getSlice().getItems().size()).isEqualTo(0);
+    }
+
+    @Test
+    public void testtInlinePresentationParcelizeDeparcelize() {
+        InlinePresentation presentation =
+                createInlinePresentation(/* createSlice */true, /* createSpec */  true);
+
+        Parcel p = Parcel.obtain();
+        presentation.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        InlinePresentation targetPresentation = InlinePresentation.CREATOR.createFromParcel(p);
+        p.recycle();
+
+        assertThat(targetPresentation.isPinned()).isEqualTo(presentation.isPinned());
+        assertThat(targetPresentation.getInlinePresentationSpec()).isEqualTo(
+                presentation.getInlinePresentationSpec());
+        assertThat(targetPresentation.getSlice().getUri()).isEqualTo(
+                presentation.getSlice().getUri());
+        assertThat(targetPresentation.getSlice().getSpec()).isEqualTo(
+                presentation.getSlice().getSpec());
+    }
+
+    private InlinePresentation createInlinePresentation(boolean createSlice, boolean createSpec) {
+        Slice slice = createSlice ? new Slice.Builder(Uri.parse("testuri"),
+                new SliceSpec("type", 1)).build() : null;
+        InlinePresentationSpec spec = createSpec ? new InlinePresentationSpec.Builder(
+                new Size(100, 100), new Size(400, 100)).build() : null;
+        return new InlinePresentation(slice, spec, /* pined */ false);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/LuhnChecksumValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/LuhnChecksumValidatorTest.java
new file mode 100644
index 0000000..4b52010
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/LuhnChecksumValidatorTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.LuhnChecksumValidator;
+import android.service.autofill.ValueFinder;
+import android.view.autofill.AutofillId;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class LuhnChecksumValidatorTest {
+
+    @Test
+    public void nullId() {
+        assertThrows(NullPointerException.class,
+                () -> new LuhnChecksumValidator((AutofillId[]) null));
+    }
+
+    @Test
+    public void nullAndOtherId() {
+        assertThrows(NullPointerException.class,
+                () -> new LuhnChecksumValidator(new AutofillId(1), null));
+    }
+
+    @Test
+    public void duplicateFields() {
+        AutofillId id = new AutofillId(1);
+
+        // duplicate fields are allowed
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id, id);
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        // 5 is a valid checksum for 0005000
+        when(finder.findByAutofillId(id)).thenReturn("0005");
+        assertThat(validator.isValid(finder)).isTrue();
+
+        // 6 is a not a valid checksum for 0006000
+        when(finder.findByAutofillId(id)).thenReturn("0006");
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+
+    @Test
+    public void leadingZerosAreIgnored() {
+        AutofillId id = new AutofillId(1);
+
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("7992739871-3");
+        assertThat(validator.isValid(finder)).isTrue();
+
+        when(finder.findByAutofillId(id)).thenReturn("07992739871-3");
+        assertThat(validator.isValid(finder)).isTrue();
+    }
+
+    @Test
+    public void onlyOneChecksumValid() {
+        AutofillId id = new AutofillId(1);
+
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        for (int i = 0; i < 10; i++) {
+            when(finder.findByAutofillId(id)).thenReturn("7992739871-" + i);
+            assertThat(validator.isValid(finder)).isEqualTo(i == 3);
+        }
+    }
+
+    @Test
+    public void nullAutofillValuesCauseFailure() {
+        AutofillId id1 = new AutofillId(1);
+        AutofillId id2 = new AutofillId(2);
+        AutofillId id3 = new AutofillId(3);
+
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id1, id2, id3);
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("7992739871");
+        when(finder.findByAutofillId(id2)).thenReturn(null);
+        when(finder.findByAutofillId(id3)).thenReturn("3");
+
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+
+    @Test
+    public void nonDigits() {
+        AutofillId id = new AutofillId(1);
+
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
+
+        ValueFinder finder = mock(ValueFinder.class);
+        when(finder.findByAutofillId(id)).thenReturn("a7B9^9\n2 7{3\b9\08\uD83C\uDF2D7-1_3$");
+        assertThat(validator.isValid(finder)).isTrue();
+    }
+
+    @Test
+    public void multipleFieldNumber() {
+        AutofillId id1 = new AutofillId(1);
+        AutofillId id2 = new AutofillId(2);
+
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id1, id2);
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("7992739871");
+        when(finder.findByAutofillId(id2)).thenReturn("3");
+        assertThat(validator.isValid(finder)).isTrue();
+
+        when(finder.findByAutofillId(id2)).thenReturn("2");
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/RegexValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/RegexValidatorTest.java
new file mode 100644
index 0000000..d7f8627
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/RegexValidatorTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.ValueFinder;
+import android.view.autofill.AutofillId;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class RegexValidatorTest {
+
+    @Test
+    public void allNullConstructor() {
+        assertThrows(NullPointerException.class, () -> new RegexValidator(null, null));
+    }
+
+    @Test
+    public void nullRegexConstructor() {
+        assertThrows(NullPointerException.class,
+                () -> new RegexValidator(new AutofillId(1), null));
+    }
+
+    @Test
+    public void nullAutofillIdConstructor() {
+        assertThrows(NullPointerException.class,
+                () -> new RegexValidator(null, Pattern.compile(".")));
+    }
+
+    @Test
+    public void unknownField() {
+        AutofillId unknownId = new AutofillId(42);
+
+        RegexValidator validator = new RegexValidator(unknownId, Pattern.compile(".*"));
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(unknownId)).thenReturn(null);
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+
+    @Test
+    public void singleFieldValid() {
+        AutofillId creditCardFieldId = new AutofillId(1);
+        RegexValidator validator = new RegexValidator(creditCardFieldId,
+                Pattern.compile("^\\s*\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?(\\d{4})\\s*$"));
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("1234 5678 9012 3456");
+        assertThat(validator.isValid(finder)).isTrue();
+
+        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("invalid");
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+
+    @Test
+    public void singleFieldInvalid() {
+        AutofillId id = new AutofillId(1);
+        RegexValidator validator = new RegexValidator(id, Pattern.compile("\\d*"));
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("123a456");
+
+        // Regex has to match the whole value
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/SaveInfoTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/SaveInfoTest.java
new file mode 100644
index 0000000..1ad74bf
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/SaveInfoTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static android.service.autofill.SaveInfo.FLAG_DELAY_SAVE;
+import static android.service.autofill.SaveInfo.FLAG_DONT_SAVE_ON_FINISH;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.InternalSanitizer;
+import android.service.autofill.Sanitizer;
+import android.service.autofill.SaveInfo;
+import android.view.autofill.AutofillId;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class SaveInfoTest {
+
+    private final AutofillId mId = new AutofillId(42);
+    private final AutofillId[] mIdArray = { mId };
+    private final InternalSanitizer mSanitizer = mock(InternalSanitizer.class);
+
+    @Test
+    public void testRequiredIdsBuilder_null() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, null));
+    }
+
+    @Test
+    public void testRequiredIdsBuilder_empty() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, new AutofillId[] {}));
+    }
+
+    @Test
+    public void testRequiredIdsBuilder_nullEntry() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                        new AutofillId[] { null }));
+    }
+
+    @Test
+    public void testBuild_noOptionalIds() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC);
+        assertThrows(IllegalStateException.class, ()-> builder.build());
+    }
+
+    @Test
+    public void testSetOptionalIds_null() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
+        assertThrows(IllegalArgumentException.class, ()-> builder.setOptionalIds(null));
+    }
+
+    @Test
+    public void testSetOptional_empty() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setOptionalIds(new AutofillId[] {}));
+    }
+
+    @Test
+    public void testSetOptional_nullEntry() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setOptionalIds(new AutofillId[] { null }));
+    }
+
+    @Test
+    public void testAddSanitizer_illegalArgs() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
+        // Null sanitizer
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(null, mId));
+        // Invalid sanitizer class
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mock(Sanitizer.class), mId));
+        // Null ids
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mSanitizer, (AutofillId[]) null));
+        // Empty ids
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mSanitizer, new AutofillId[] {}));
+        // Repeated ids
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mSanitizer, new AutofillId[] {mId, mId}));
+    }
+
+    @Test
+    public void testAddSanitizer_sameIdOnDifferentCalls() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
+        builder.addSanitizer(mSanitizer, mId);
+        assertThrows(IllegalArgumentException.class, () -> builder.addSanitizer(mSanitizer, mId));
+    }
+
+    @Test
+    public void testBuild_invalid() {
+        // No nothing
+        assertThrows(IllegalStateException.class, () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC)
+                .build());
+        // Flag only, but invalid flag
+        assertThrows(IllegalStateException.class, () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC)
+                .setFlags(FLAG_DONT_SAVE_ON_FINISH).build());
+    }
+
+    @Test
+    public void testBuild_valid() {
+        // Required ids
+        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, mIdArray)
+                .build()).isNotNull();
+
+        // Optional ids
+        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC).setOptionalIds(mIdArray)
+                .build()).isNotNull();
+
+        // Delayed save
+        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC).setFlags(FLAG_DELAY_SAVE)
+                .build()).isNotNull();
+    }
+
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceDisabledForSureTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceDisabledForSureTest.java
new file mode 100644
index 0000000..93a0df5
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceDisabledForSureTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.autofillservice.cts.unittests;
+
+import static android.autofillservice.cts.activities.OnCreateServiceStatusVerifierActivity.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.Helper.disableAutofillService;
+import static android.autofillservice.cts.testcore.Helper.enableAutofillService;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.activities.OnCreateServiceStatusVerifierActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.Helper;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test case that guarantee the service is disabled before the activity launches.
+ */
+@AppModeFull(reason = "Service-specific test")
+public class ServiceDisabledForSureTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<OnCreateServiceStatusVerifierActivity> {
+
+    private static final String TAG = "ServiceDisabledForSureTest";
+
+    private OnCreateServiceStatusVerifierActivity mActivity;
+
+    @BeforeClass
+    public static void resetService() {
+        disableAutofillService(sContext);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity> getActivityRule() {
+        return new AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity>(
+                OnCreateServiceStatusVerifierActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Override
+    protected void prepareServicePreTest() {
+        // Doesn't need to prepare the service - that was already taken care of in a @BeforeClass -
+        // but to guarantee the test finishes in the proper state
+        Log.v(TAG, "prepareServicePreTest(): not doing anything");
+        mSafeCleanerRule.run(() ->assertThat(mActivity.getAutofillManager().isEnabled()).isFalse());
+    }
+
+    @Test
+    public void testIsAutofillEnabled() throws Exception {
+        mActivity.assertServiceStatusOnCreate(false);
+
+        final AutofillManager afm = mActivity.getAutofillManager();
+        Helper.assertAutofillEnabled(afm, false);
+
+        enableAutofillService(mContext, SERVICE_NAME);
+        Helper.assertAutofillEnabled(afm, true);
+
+        disableAutofillService(mContext);
+        Helper.assertAutofillEnabled(afm, false);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceEnabledForSureTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceEnabledForSureTest.java
new file mode 100644
index 0000000..fab4841
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceEnabledForSureTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.autofillservice.cts.unittests;
+
+import static android.autofillservice.cts.activities.OnCreateServiceStatusVerifierActivity.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.Helper.disableAutofillService;
+import static android.autofillservice.cts.testcore.Helper.enableAutofillService;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.activities.OnCreateServiceStatusVerifierActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.Helper;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test case that guarantee the service is enabled before the activity launches.
+ */
+public class ServiceEnabledForSureTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<OnCreateServiceStatusVerifierActivity> {
+
+    private static final String TAG = "ServiceEnabledForSureTest";
+
+    private OnCreateServiceStatusVerifierActivity mActivity;
+
+    @BeforeClass
+    public static void resetService() {
+        enableAutofillService(sContext, SERVICE_NAME);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity> getActivityRule() {
+        return new AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity>(
+                OnCreateServiceStatusVerifierActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Override
+    protected void prepareServicePreTest() {
+        // Doesn't need to prepare the service - that was already taken care of in a @BeforeClass -
+        // but to guarantee the test finishes in the proper state
+        Log.v(TAG, "prepareServicePreTest(): not doing anything");
+        mSafeCleanerRule.run(() ->assertThat(mActivity.getAutofillManager().isEnabled()).isTrue());
+    }
+
+    @Test
+    public void testIsAutofillEnabled() throws Exception {
+        mActivity.assertServiceStatusOnCreate(true);
+
+        final AutofillManager afm = mActivity.getAutofillManager();
+        Helper.assertAutofillEnabled(afm, true);
+
+        disableAutofillService(mContext);
+        Helper.assertAutofillEnabled(afm, false);
+
+        enableAutofillService(mContext, SERVICE_NAME);
+        Helper.assertAutofillEnabled(afm, true);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/TextValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/TextValueSanitizerTest.java
new file mode 100644
index 0000000..3d4df93
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/TextValueSanitizerTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.TextValueSanitizer;
+import android.view.autofill.AutofillValue;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class TextValueSanitizerTest {
+
+    @Test
+    public void testConstructor_nullValues() {
+        assertThrows(NullPointerException.class,
+                () -> new TextValueSanitizer(Pattern.compile("42"), null));
+        assertThrows(NullPointerException.class,
+                () -> new TextValueSanitizer(null, "42"));
+    }
+
+    @Test
+    public void testSanitize_nullValue() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
+        assertThat(sanitizer.sanitize(null)).isNull();
+    }
+
+    @Test
+    public void testSanitize_nonTextValue() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
+        final AutofillValue value = AutofillValue.forToggle(true);
+        assertThat(sanitizer.sanitize(value)).isNull();
+    }
+
+    @Test
+    public void testSanitize_badRegex() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
+                "$2"); // invalid group
+        final AutofillValue value = AutofillValue.forText("blah 42  blaH");
+        assertThat(sanitizer.sanitize(value)).isNull();
+    }
+
+    @Test
+    public void testSanitize_valueMismatch() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "xxx");
+        final AutofillValue value = AutofillValue.forText("43");
+        assertThat(sanitizer.sanitize(value)).isNull();
+    }
+
+    @Test
+    public void testSanitize_simpleMatch() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"),
+                "forty-two");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42")).getTextValue())
+            .isEqualTo("forty-two");
+    }
+
+    @Test
+    public void testSanitize_multipleMatches() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
+                "Number");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("blah 42  blaH")).getTextValue())
+            .isEqualTo("NumberNumber");
+    }
+
+    @Test
+    public void testSanitize_groupSubstitutionMatch() {
+        final TextValueSanitizer sanitizer =
+                new TextValueSanitizer(Pattern.compile("\\s*(\\d*)\\s*"), "$1");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("  42 ")).getTextValue())
+                .isEqualTo("42");
+    }
+
+    @Test
+    public void testSanitize_groupSubstitutionMatch_withOptionalGroup() {
+        final TextValueSanitizer sanitizer =
+                new TextValueSanitizer(Pattern.compile("(\\d*)\\s?(\\d*)?"), "$1$2");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42 108")).getTextValue())
+                .isEqualTo("42108");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42108")).getTextValue())
+                .isEqualTo("42108");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42")).getTextValue())
+                .isEqualTo("42");
+        final TextValueSanitizer ccSanitizer = new TextValueSanitizer(Pattern.compile(
+                "^(\\d{4,5})-?\\s?(\\d{4,6})-?\\s?(\\d{4,5})" // first 3 are required
+                        + "-?\\s?((?:\\d{4,5})?)-?\\s?((?:\\d{3,5})?)$"), // last 2 are optional
+                "$1$2$3$4$5");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("1111 2222 3333 4444 5555")).getTextValue())
+                        .isEqualTo("11112222333344445555");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("11111-222222-33333-44444-55555")).getTextValue())
+                        .isEqualTo("11111222222333334444455555");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("1111 2222 3333 4444")).getTextValue())
+                        .isEqualTo("1111222233334444");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("11111-222222-33333-44444-")).getTextValue())
+                        .isEqualTo("111112222223333344444");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("1111 2222 3333")).getTextValue())
+                        .isEqualTo("111122223333");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("11111-222222-33333 ")).getTextValue())
+                        .isEqualTo("1111122222233333");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/UserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/UserDataTest.java
new file mode 100644
index 0000000..ed5164c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/UserDataTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.UserData;
+
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+
+import com.google.common.base.Strings;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class UserDataTest {
+
+    private static final Context sContext = getInstrumentation().getTargetContext();
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
+            new SettingsStateChangerRule(sContext,
+                    AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxCategoriesSizeChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "2");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "4");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMinValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "4");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
+
+
+    private final String mShortValue = Strings.repeat("k", UserData.getMinValueLength() - 1);
+    private final String mLongValue = "LONG VALUE, Y U NO SHORTER"
+            + Strings.repeat("?", UserData.getMaxValueLength());
+    private final String mId = "4815162342";
+    private final String mCategoryId = "id1";
+    private final String mCategoryId2 = "id2";
+    private final String mCategoryId3 = "id3";
+    private final String mValue = mShortValue + "-1";
+    private final String mValue2 = mShortValue + "-2";
+    private final String mValue3 = mShortValue + "-3";
+    private final String mValue4 = mShortValue + "-4";
+    private final String mValue5 = mShortValue + "-5";
+
+    private UserData.Builder mBuilder;
+
+    @Before
+    public void setFixtures() {
+        mBuilder = new UserData.Builder(mId, mValue, mCategoryId);
+    }
+
+    @Test
+    public void testBuilder_invalid() {
+        assertThrows(NullPointerException.class,
+                () -> new UserData.Builder(null, mValue, mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder("", mValue, mCategoryId));
+        assertThrows(NullPointerException.class,
+                () -> new UserData.Builder(mId, null, mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder(mId, "", mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder(mId, mShortValue, mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder(mId, mLongValue, mCategoryId));
+        assertThrows(NullPointerException.class, () -> new UserData.Builder(mId, mValue, null));
+        assertThrows(IllegalArgumentException.class, () -> new UserData.Builder(mId, mValue, ""));
+    }
+
+    @Test
+    public void testAdd_invalid() {
+        assertThrows(NullPointerException.class, () -> mBuilder.add(null, mCategoryId));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add("", mCategoryId));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mShortValue, mCategoryId));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mLongValue, mCategoryId));
+        assertThrows(NullPointerException.class, () -> mBuilder.add(mValue, null));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mValue, ""));
+    }
+
+    @Test
+    public void testAdd_duplicatedValue() {
+        assertThat(new UserData.Builder(mId, mValue, mCategoryId).add(mValue, mCategoryId).build())
+                .isNotNull();
+        assertThat(new UserData.Builder(mId, mValue, mCategoryId).add(mValue, mCategoryId2).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testAdd_maximumCategoriesReached() {
+        // Max is 2; one was added in the constructor
+        mBuilder.add(mValue2, mCategoryId2);
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue3, mCategoryId3));
+    }
+
+    @Test
+    public void testAdd_maximumUserDataReached() {
+        // Max is 4; one was added in the constructor
+        mBuilder.add(mValue2, mCategoryId);
+        mBuilder.add(mValue3, mCategoryId);
+        mBuilder.add(mValue4, mCategoryId2);
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue5, mCategoryId2));
+    }
+
+    @Test
+    public void testSetFcAlgorithm() {
+        final UserData userData = mBuilder.setFieldClassificationAlgorithm("algo_mas", null)
+                .build();
+        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo("algo_mas");
+    }
+
+    @Test
+    public void testSetFcAlgorithmForCategory_invalid() {
+        assertThrows(NullPointerException.class, () -> mBuilder
+                .setFieldClassificationAlgorithmForCategory(null, "algo_mas", null));
+    }
+
+    @Test
+    public void testSetFcAlgorithmForCateogry() {
+        final UserData userData = mBuilder.setFieldClassificationAlgorithmForCategory(
+                mCategoryId, "algo_mas", null).build();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId)).isEqualTo(
+                "algo_mas");
+    }
+
+    @Test
+    public void testBuild_valid() {
+        final UserData userData = mBuilder.build();
+        assertThat(userData).isNotNull();
+        assertThat(userData.getId()).isEqualTo(mId);
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId)).isNull();
+    }
+
+    @Test
+    public void testGetFcAlgorithmForCategory_invalid() {
+        final UserData userData = mBuilder.setFieldClassificationAlgorithm("algo_mas", null)
+                .build();
+        assertThrows(NullPointerException.class, () -> userData
+                .getFieldClassificationAlgorithmForCategory(null));
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        testBuild_valid();
+
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue, mCategoryId2));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setFieldClassificationAlgorithmForCategory(mCategoryId,
+                        "algo_mas", null));
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/ValidatorsTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/ValidatorsTest.java
new file mode 100644
index 0000000..22bacb8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/ValidatorsTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static android.service.autofill.Validators.and;
+import static android.service.autofill.Validators.not;
+import static android.service.autofill.Validators.or;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.InternalValidator;
+import android.service.autofill.Validator;
+import android.service.autofill.ValueFinder;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class ValidatorsTest {
+
+    @Mock private Validator mInvalidValidator;
+    @Mock private ValueFinder mValueFinder;
+    @Mock private InternalValidator mValidValidator;
+    @Mock private InternalValidator mValidValidator2;
+
+    @Test
+    public void testAnd_null() {
+        assertThrows(NullPointerException.class, () -> and((Validator) null));
+        assertThrows(NullPointerException.class, () -> and(mValidValidator, null));
+        assertThrows(NullPointerException.class, () -> and(null, mValidValidator));
+    }
+
+    @Test
+    public void testAnd_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> and(mValidValidator, mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator, mValidValidator));
+    }
+
+    @Test
+    public void testAnd_firstFailed() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isFalse();
+        verify(mValidValidator2, never()).isValid(mValueFinder);
+    }
+
+    @Test
+    public void testAnd_firstPassedSecondFailed() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        doReturn(false).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isFalse();
+    }
+
+    @Test
+    public void testAnd_AllPassed() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        doReturn(true).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isTrue();
+    }
+
+    @Test
+    public void testOr_null() {
+        assertThrows(NullPointerException.class, () -> or((Validator) null));
+        assertThrows(NullPointerException.class, () -> or(mValidValidator, null));
+        assertThrows(NullPointerException.class, () -> or(null, mValidValidator));
+    }
+
+    @Test
+    public void testOr_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> or(mValidValidator, mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator, mValidValidator));
+    }
+
+    @Test
+    public void testOr_AllFailed() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        doReturn(false).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isFalse();
+    }
+
+    @Test
+    public void testOr_firstPassed() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isTrue();
+        verify(mValidValidator2, never()).isValid(mValueFinder);
+    }
+
+    @Test
+    public void testOr_secondPassed() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        doReturn(true).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isTrue();
+    }
+
+    @Test
+    public void testNot_null() {
+        assertThrows(IllegalArgumentException.class, () -> not(null));
+    }
+
+    @Test
+    public void testNot_invalidClass() {
+        assertThrows(IllegalArgumentException.class, () -> not(mInvalidValidator));
+    }
+
+    @Test
+    public void testNot_falseToTrue() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
+        assertThat(notValidator.isValid(mValueFinder)).isTrue();
+    }
+
+    @Test
+    public void testNot_trueToFalse() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
+        assertThat(notValidator.isValid(mValueFinder)).isFalse();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/VisibilitySetterActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/VisibilitySetterActionTest.java
new file mode 100644
index 0000000..5bb8f2b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/VisibilitySetterActionTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.autofillservice.cts.unittests;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.VisibilitySetterAction;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class VisibilitySetterActionTest {
+
+    private static final Context sContext = getInstrumentation().getTargetContext();
+    private final ViewGroup mRootView = new ViewGroup(sContext) {
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {}
+    };
+
+    @Test
+    public void testValidVisibilities() {
+        assertThat(new VisibilitySetterAction.Builder(42, View.VISIBLE).build()).isNotNull();
+        assertThat(new VisibilitySetterAction.Builder(42, View.GONE).build()).isNotNull();
+        assertThat(new VisibilitySetterAction.Builder(42, View.INVISIBLE).build()).isNotNull();
+    }
+
+    @Test
+    public void testInvalidVisibilities() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new VisibilitySetterAction.Builder(42, 666).build());
+        final VisibilitySetterAction.Builder validBuilder =
+                new VisibilitySetterAction.Builder(42, View.VISIBLE);
+        assertThrows(IllegalArgumentException.class,
+                () -> validBuilder.setVisibility(108, 666).build());
+    }
+
+    @Test
+    public void testOneChild() {
+        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
+                .build();
+        final View view = new View(sContext);
+        view.setId(42);
+        view.setVisibility(View.GONE);
+        mRootView.addView(view);
+
+        action.onClick(mRootView);
+
+        assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testOneChildAddedTwice() {
+        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
+                .setVisibility(42, View.INVISIBLE)
+                .build();
+        final View view = new View(sContext);
+        view.setId(42);
+        view.setVisibility(View.GONE);
+        mRootView.addView(view);
+
+        action.onClick(mRootView);
+
+        assertThat(view.getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void testMultipleChildren() {
+        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
+                .setVisibility(108, View.INVISIBLE)
+                .build();
+        final View view1 = new View(sContext);
+        view1.setId(42);
+        view1.setVisibility(View.GONE);
+        mRootView.addView(view1);
+
+        final View view2 = new View(sContext);
+        view2.setId(108);
+        view2.setVisibility(View.GONE);
+        mRootView.addView(view2);
+
+        action.onClick(mRootView);
+
+        assertThat(view1.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(view2.getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        final VisibilitySetterAction.Builder builder =
+                new VisibilitySetterAction.Builder(42, View.VISIBLE);
+
+        assertThat(builder.build()).isNotNull();
+        assertThrows(IllegalStateException.class, () -> builder.build());
+        assertThrows(IllegalStateException.class, () -> builder.setVisibility(108, View.GONE));
+    }
+}
diff --git a/tests/backup/OWNERS b/tests/backup/OWNERS
index e0e5e22..9391645 100644
--- a/tests/backup/OWNERS
+++ b/tests/backup/OWNERS
@@ -1,9 +1,12 @@
-# Bug component: 41666
+# Bug component: 656484
 # Use this reviewer by default.
-br-framework-team+reviews@google.com
+br-platform-dev@google.com
 
-alsutton@google.com
-anniemeng@google.com
-brufino@google.com
-nathch@google.com
+# People who can approve changes for submission.
 rthakohov@google.com
+tobiast@google.com
+jstemmer@google.com
+aabhinav@google.com
+philippov@google.com
+niagra@google.com
+niamhfw@google.com
diff --git a/tests/backup/app/fullbackup/AndroidManifest.xml b/tests/backup/app/fullbackup/AndroidManifest.xml
index 138c774..7f639f9 100644
--- a/tests/backup/app/fullbackup/AndroidManifest.xml
+++ b/tests/backup/app/fullbackup/AndroidManifest.xml
@@ -16,28 +16,28 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.backup.app" >
+     package="android.backup.app">
 
-    <application
-        android:allowBackup="true"
-        android:backupAgent="FullBackupBackupAgent"
-        android:label="Android Backup CTS App"
-        android:fullBackupOnly="true">
-        <uses-library android:name="android.test.runner" />
+    <application android:allowBackup="true"
+         android:backupAgent="FullBackupBackupAgent"
+         android:label="Android Backup CTS App"
+         android:fullBackupOnly="true">
+        <uses-library android:name="android.test.runner"/>
 
 
-        <activity
-            android:name=".MainActivity"
-            android:label="Android Backup CTS App" >
+        <activity android:name=".MainActivity"
+             android:label="Android Backup CTS App"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <receiver android:name=".WakeUpReceiver">
+        <receiver android:name=".WakeUpReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.backup.app.ACTION_WAKE_UP" />
+                <action android:name="android.backup.app.ACTION_WAKE_UP"/>
             </intent-filter>
         </receiver>
 
diff --git a/tests/backup/app/keyvalue/AndroidManifest.xml b/tests/backup/app/keyvalue/AndroidManifest.xml
index 3ed302d..c36d70c 100644
--- a/tests/backup/app/keyvalue/AndroidManifest.xml
+++ b/tests/backup/app/keyvalue/AndroidManifest.xml
@@ -16,20 +16,19 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.backup.kvapp" >
+     package="android.backup.kvapp">
 
-    <application
-        android:allowBackup="true"
-        android:backupAgent="android.backup.app.KeyValueBackupAgent"
-        android:label="Android Key Value Backup CTS App">
-        <uses-library android:name="android.test.runner" />
+    <application android:allowBackup="true"
+         android:backupAgent="android.backup.app.KeyValueBackupAgent"
+         android:label="Android Key Value Backup CTS App">
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name="android.backup.app.MainActivity"
-            android:label="Android Key Value Backup CTS App" >
+        <activity android:name="android.backup.app.MainActivity"
+             android:label="Android Key Value Backup CTS App"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/bugreport/Android.bp b/tests/bugreport/Android.bp
index 40e8975..d008efb 100644
--- a/tests/bugreport/Android.bp
+++ b/tests/bugreport/Android.bp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-android_test_helper_app {
+android_test {
     name: "CtsBugreportTestCases",
     defaults: ["cts_defaults"],
     static_libs: [
@@ -25,4 +25,8 @@
         ],
     srcs: ["src/**/*.java"],
     sdk_version: "test_current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
 }
diff --git a/tests/bugreport/AndroidTest.xml b/tests/bugreport/AndroidTest.xml
index b46682a..bdb66bb 100644
--- a/tests/bugreport/AndroidTest.xml
+++ b/tests/bugreport/AndroidTest.xml
@@ -16,7 +16,7 @@
   -->
 <configuration description="Bugreport Manager tests">
     <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="auto" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
@@ -26,5 +26,6 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.bugreport.cts" />
+        <option name="runtime-hint" value="3m40s" />
     </test>
 </configuration>
diff --git a/tests/bugreport/OWNERS b/tests/bugreport/OWNERS
index 32da65d..6ac6141 100644
--- a/tests/bugreport/OWNERS
+++ b/tests/bugreport/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 153446
 abkaur@google.com
 nandana@google.com
diff --git a/tests/bugreport/TEST_MAPPING b/tests/bugreport/TEST_MAPPING
new file mode 100644
index 0000000..4244967
--- /dev/null
+++ b/tests/bugreport/TEST_MAPPING
@@ -0,0 +1,17 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsBugreportTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsBugreportTestCases"
+    }
+  ]
+}
diff --git a/tests/bugreport/src/android/bugreport/cts/BugreportManagerTest.java b/tests/bugreport/src/android/bugreport/cts/BugreportManagerTest.java
index 0a9b3f8..bea0dd1 100644
--- a/tests/bugreport/src/android/bugreport/cts/BugreportManagerTest.java
+++ b/tests/bugreport/src/android/bugreport/cts/BugreportManagerTest.java
@@ -29,6 +29,7 @@
 import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
@@ -91,6 +92,7 @@
         assertThat(screenshot).isNull();
     }
 
+    @LargeTest
     @Test
     public void testFullBugreport() throws Exception {
         Pair<String, String> brFiles = triggerBugreport(BugreportParams.BUGREPORT_MODE_FULL);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
index 4374db9..80e8f69 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -2520,6 +2520,17 @@
                     CaptureRequest.DISTORTION_CORRECTION_MODE_OFF);
         }
 
+        // Scaler settings
+        if (mStaticInfo.areKeysAvailable(
+                CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES)) {
+            List<Integer> rotateAndCropModes = Arrays.asList(toObject(
+                props.get(CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES)));
+            if (rotateAndCropModes.contains(SCALER_ROTATE_AND_CROP_AUTO)) {
+                mCollector.expectKeyValueEquals(request, SCALER_ROTATE_AND_CROP,
+                        CaptureRequest.SCALER_ROTATE_AND_CROP_AUTO);
+            }
+        }
+
         // Check JPEG quality
         if (mStaticInfo.isColorOutputSupported()) {
             mCollector.expectKeyValueNotNull(request, JPEG_QUALITY);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
index a5c8978..8e8f421 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -733,6 +733,10 @@
             waiverKeys.add(CaptureResult.CONTROL_EXTENDED_SCENE_MODE);
         }
 
+        if (!staticInfo.isRotateAndCropSupported()) {
+            waiverKeys.add(CaptureResult.SCALER_ROTATE_AND_CROP);
+        }
+
         if (staticInfo.isHardwareLevelAtLeastFull()) {
             return waiverKeys;
         }
@@ -847,6 +851,7 @@
         waiverKeys.add(CaptureResult.STATISTICS_FACE_DETECT_MODE);
         waiverKeys.add(CaptureResult.FLASH_MODE);
         waiverKeys.add(CaptureResult.SCALER_CROP_REGION);
+        waiverKeys.add(CaptureResult.SCALER_ROTATE_AND_CROP);
 
         return waiverKeys;
     }
@@ -1019,6 +1024,7 @@
         resultKeys.add(CaptureResult.NOISE_REDUCTION_MODE);
         resultKeys.add(CaptureResult.REQUEST_PIPELINE_DEPTH);
         resultKeys.add(CaptureResult.SCALER_CROP_REGION);
+        resultKeys.add(CaptureResult.SCALER_ROTATE_AND_CROP);
         resultKeys.add(CaptureResult.SENSOR_EXPOSURE_TIME);
         resultKeys.add(CaptureResult.SENSOR_FRAME_DURATION);
         resultKeys.add(CaptureResult.SENSOR_SENSITIVITY);
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 7bf1f4c..2a8ef17 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -2315,6 +2315,53 @@
         }
     }
 
+    /**
+     * Check rotate-and-crop camera reporting.
+     * Every device must report NONE; if actually supporting feature, must report NONE, 90, AUTO at
+     * least.
+     */
+    @Test
+    public void testRotateAndCropCharacteristics() {
+        for (int i = 0; i < mAllCameraIds.length; i++) {
+            Log.i(TAG, "testRotateAndCropCharacteristics: Testing camera ID " + mAllCameraIds[i]);
+
+            CameraCharacteristics c = mCharacteristics.get(i);
+
+            if (!arrayContains(mCameraIdsUnderTest, mAllCameraIds[i])) {
+                // Skip hidden physical cameras
+                continue;
+            }
+
+            int[] availableRotateAndCropModes = c.get(
+                    CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES);
+            assertTrue("availableRotateAndCropModes must not be null",
+                     availableRotateAndCropModes != null);
+            boolean foundAuto = false;
+            boolean foundNone = false;
+            boolean found90 = false;
+            for (int mode :  availableRotateAndCropModes) {
+                switch(mode) {
+                    case CameraCharacteristics.SCALER_ROTATE_AND_CROP_NONE:
+                        foundNone = true;
+                        break;
+                    case CameraCharacteristics.SCALER_ROTATE_AND_CROP_90:
+                        found90 = true;
+                        break;
+                    case CameraCharacteristics.SCALER_ROTATE_AND_CROP_AUTO:
+                        foundAuto = true;
+                        break;
+                }
+            }
+            if (availableRotateAndCropModes.length > 1) {
+                assertTrue("To support SCALER_ROTATE_AND_CROP: NONE, 90, and AUTO must be included",
+                        foundNone && found90 && foundAuto);
+            } else {
+                assertTrue("If only one SCALER_ROTATE_AND_CROP value is supported, it must be NONE",
+                        foundNone);
+            }
+        }
+    }
+
     private boolean matchParametersToCharacteritics(Camera.Parameters params,
             Camera.CameraInfo info, CameraCharacteristics ch) {
         Integer facing = ch.get(CameraCharacteristics.LENS_FACING);
diff --git a/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java b/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
index 9caf365..b093e6c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
@@ -127,7 +127,7 @@
             if (hasAccess) {
                 fail("Unexpected exception" + e);
             } else {
-                assertThat(e.getReason()).isSameAs(CameraAccessException.CAMERA_DISABLED);
+                assertThat(e.getReason()).isSameInstanceAs(CameraAccessException.CAMERA_DISABLED);
             }
         }
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
index 94282c8..45d341b 100644
--- a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -152,9 +152,11 @@
             double[] cameraCloseTimes = new double[NUM_TEST_LOOPS];
             double[] cameraLaunchTimes = new double[NUM_TEST_LOOPS];
             try {
-                mTestRule.setStaticInfo(new StaticMetadata(
-                        mTestRule.getCameraManager().getCameraCharacteristics(id)));
-                if (mTestRule.getStaticInfo().isColorOutputSupported()) {
+                CameraCharacteristics ch =
+                        mTestRule.getCameraManager().getCameraCharacteristics(id);
+                mTestRule.setStaticInfo(new StaticMetadata(ch));
+                boolean isColorOutputSupported = mTestRule.getStaticInfo().isColorOutputSupported();
+                if (isColorOutputSupported) {
                     initializeImageReader(id, ImageFormat.YUV_420_888);
                 } else {
                     assertTrue("Depth output must be supported if regular output isn't!",
@@ -179,14 +181,15 @@
                         cameraOpenTimes[i] = openTimeMs - startTimeMs;
 
                         // Blocking configure outputs.
-                        configureReaderAndPreviewOutputs();
+                        CaptureRequest previewRequest =
+                                configureReaderAndPreviewOutputs(id, isColorOutputSupported);
                         configureTimeMs = SystemClock.elapsedRealtime();
                         configureStreamTimes[i] = configureTimeMs - openTimeMs;
 
                         // Blocking start preview (start preview to first image arrives)
                         SimpleCaptureCallback resultListener =
                                 new SimpleCaptureCallback();
-                        blockingStartPreview(id, resultListener, imageListener);
+                        blockingStartPreview(id, resultListener, previewRequest, imageListener);
                         previewStartedTimeMs = SystemClock.elapsedRealtime();
                         startPreviewTimes[i] = previewStartedTimeMs - configureTimeMs;
                         cameraLaunchTimes[i] = previewStartedTimeMs - startTimeMs;
@@ -546,10 +549,12 @@
             double[] getResultTimes = new double[NUM_MAX_IMAGES];
             double[] frameDurationMs = new double[NUM_MAX_IMAGES-1];
             try {
-                if (!mTestRule.getAllStaticInfo().get(id).isColorOutputSupported()) {
+                StaticMetadata staticMetadata = mTestRule.getAllStaticInfo().get(id);
+                if (!staticMetadata.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                boolean useSessionKeys = isFpsRangeASessionKey(staticMetadata.getCharacteristics());
 
                 mTestRule.openDevice(id);
                 for (int i = 0; i < NUM_TEST_LOOPS; i++) {
@@ -589,7 +594,8 @@
                     prepareCaptureAndStartPreview(previewBuilder, captureBuilder,
                             mTestRule.getOrderedPreviewSizes().get(0), maxYuvSize,
                             ImageFormat.YUV_420_888, previewResultListener,
-                            sessionListener, NUM_MAX_IMAGES, imageListener);
+                            sessionListener, NUM_MAX_IMAGES, imageListener,
+                            useSessionKeys);
 
                     // Converge AE
                     CameraTestUtils.waitForAeStable(previewResultListener,
@@ -1155,31 +1161,10 @@
     }
 
     private void blockingStartPreview(String id, CaptureCallback listener,
-            SimpleImageListener imageListener) throws Exception {
-        if (mPreviewSurface == null || mTestRule.getReaderSurface() == null) {
-            throw new IllegalStateException("preview and reader surface must be initilized first");
-        }
-
-        StreamConfigurationMap config =
-                mTestRule.getStaticInfo().getCharacteristics().get(
-                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-        CaptureRequest.Builder previewBuilder =
-                mTestRule.getCamera().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
-        long minFrameDuration = Math.max(FRAME_DURATION_NS_30FPS,
-                config.getOutputMinFrameDuration(mImageReaderFormat, mPreviewSize));
-        if (mTestRule.getStaticInfo().isColorOutputSupported()) {
-            previewBuilder.addTarget(mPreviewSurface);
-            minFrameDuration = Math.max(minFrameDuration,
-                    config.getOutputMinFrameDuration(SurfaceTexture.class, mPreviewSize));
-        }
-        previewBuilder.addTarget(mTestRule.getReaderSurface());
-
-        Range<Integer> targetRange =
-                CameraTestUtils.getSuitableFpsRangeForDuration(id,
-                        minFrameDuration, mTestRule.getStaticInfo());
-        previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
+            CaptureRequest previewRequest, SimpleImageListener imageListener)
+            throws Exception {
         mTestRule.getCameraSession().setRepeatingRequest(
-                previewBuilder.build(), listener, mTestRule.getHandler());
+                previewRequest, listener, mTestRule.getHandler());
         imageListener.waitForImageAvailable(CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS);
     }
 
@@ -1187,8 +1172,8 @@
      * Setup still capture configuration and start preview.
      *
      * @param id The camera id under test
-     * @param previewRequest The capture request to be used for preview
-     * @param stillRequest The capture request to be used for still capture
+     * @param previewBuilder The capture request builder to be used for preview
+     * @param stillBuilder The capture request builder to be used for still capture
      * @param previewSz Preview size
      * @param captureSizes Still capture sizes
      * @param formats The single capture image formats
@@ -1198,7 +1183,7 @@
      * @param isHeic Capture HEIC image if true, JPEG image if false
      */
     private ImageReader[] prepareStillCaptureAndStartPreview(String id,
-            CaptureRequest.Builder previewRequest, CaptureRequest.Builder stillRequest,
+            CaptureRequest.Builder previewBuilder, CaptureRequest.Builder stillBuilder,
             Size previewSz, Size[] captureSizes, int[] formats, CaptureCallback resultListener,
             int maxNumImages, ImageReader.OnImageAvailableListener[] imageListeners,
             boolean isHeic)
@@ -1218,8 +1203,8 @@
         // Update preview size.
         updatePreviewSurface(previewSz);
 
-        StreamConfigurationMap config =
-                mTestRule.getStaticInfo().getCharacteristics().get(
+        CameraCharacteristics ch = mTestRule.getStaticInfo().getCharacteristics();
+        StreamConfigurationMap config = ch.get(
                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
         ImageReader[] readers = new ImageReader[captureSizes.length];
         List<Surface> outputSurfaces = new ArrayList<Surface>();
@@ -1236,37 +1221,66 @@
             outputSurfaces.add(readers[i].getSurface());
         }
 
+        // Configure the requests.
+        previewBuilder.addTarget(mPreviewSurface);
+        stillBuilder.addTarget(mPreviewSurface);
+        for (int i = 0; i < readers.length; i++) {
+            stillBuilder.addTarget(readers[i].getSurface());
+        }
+
         // Update target fps based on min frame durations
         Range<Integer> targetRange =
                 CameraTestUtils.getSuitableFpsRangeForDuration(id,
                 minFrameDuration, mTestRule.getStaticInfo());
-        previewRequest.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
-        stillRequest.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
+        previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
+        stillBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
 
+        CaptureRequest previewRequest = previewBuilder.build();
         mTestRule.setCameraSessionListener(new BlockingSessionCallback());
-        mTestRule.setCameraSession(CameraTestUtils.configureCameraSession(
-                mTestRule.getCamera(), outputSurfaces,
-                mTestRule.getCameraSessionListener(), mTestRule.getHandler()));
-
-        // Configure the requests.
-        previewRequest.addTarget(mPreviewSurface);
-        stillRequest.addTarget(mPreviewSurface);
-        for (int i = 0; i < readers.length; i++) {
-            stillRequest.addTarget(readers[i].getSurface());
-        }
+        boolean useSessionKeys = isFpsRangeASessionKey(ch);
+        configureAndSetCameraSession(outputSurfaces, useSessionKeys, previewRequest);
 
         // Start preview.
         mTestRule.getCameraSession().setRepeatingRequest(
-                previewRequest.build(), resultListener, mTestRule.getHandler());
+                previewRequest, resultListener, mTestRule.getHandler());
 
         return readers;
     }
 
     /**
+     * Helper function to check if TARGET_FPS_RANGE is a session parameter
+     */
+    private boolean isFpsRangeASessionKey(CameraCharacteristics ch) {
+        List<CaptureRequest.Key<?>> sessionKeys = ch.getAvailableSessionKeys();
+        return sessionKeys != null &&
+                sessionKeys.contains(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
+    }
+
+    /**
+     * Helper function to configure camera session using parameters provided.
+     */
+    private void configureAndSetCameraSession(List<Surface> surfaces,
+            boolean useInitialRequest, CaptureRequest initialRequest)
+            throws CameraAccessException {
+        CameraCaptureSession cameraSession;
+        if (useInitialRequest) {
+            cameraSession = CameraTestUtils.configureCameraSessionWithParameters(
+                mTestRule.getCamera(), surfaces,
+                mTestRule.getCameraSessionListener(), mTestRule.getHandler(),
+                initialRequest);
+        } else {
+            cameraSession = CameraTestUtils.configureCameraSession(
+                mTestRule.getCamera(), surfaces,
+                mTestRule.getCameraSessionListener(), mTestRule.getHandler());
+        }
+        mTestRule.setCameraSession(cameraSession);
+    }
+
+    /**
      * Setup single capture configuration and start preview.
      *
-     * @param previewRequest The capture request to be used for preview
-     * @param stillRequest The capture request to be used for still capture
+     * @param previewBuilder The capture request builder to be used for preview
+     * @param stillBuilder The capture request builder to be used for still capture
      * @param previewSz Preview size
      * @param captureSz Still capture size
      * @param format The single capture image format
@@ -1274,11 +1288,13 @@
      * @param sessionListener Session listener
      * @param maxNumImages The max number of images set to the image reader
      * @param imageListener The single capture capture image listener
+     * @param useSessionKeys Create capture session using session keys from previewRequest
      */
-    private void prepareCaptureAndStartPreview(CaptureRequest.Builder previewRequest,
-            CaptureRequest.Builder stillRequest, Size previewSz, Size captureSz, int format,
+    private void prepareCaptureAndStartPreview(CaptureRequest.Builder previewBuilder,
+            CaptureRequest.Builder stillBuilder, Size previewSz, Size captureSz, int format,
             CaptureCallback resultListener, CameraCaptureSession.StateCallback sessionListener,
-            int maxNumImages, ImageReader.OnImageAvailableListener imageListener) throws Exception {
+            int maxNumImages, ImageReader.OnImageAvailableListener imageListener,
+            boolean  useSessionKeys) throws Exception {
         if ((captureSz == null) || (imageListener == null)) {
             throw new IllegalArgumentException("Invalid capture size or image listener!");
         }
@@ -1303,18 +1319,18 @@
         } else {
             mTestRule.setCameraSessionListener(new BlockingSessionCallback(sessionListener));
         }
-        mTestRule.setCameraSession(CameraTestUtils.configureCameraSession(
-                mTestRule.getCamera(), outputSurfaces,
-                mTestRule.getCameraSessionListener(), mTestRule.getHandler()));
 
         // Configure the requests.
-        previewRequest.addTarget(mPreviewSurface);
-        stillRequest.addTarget(mPreviewSurface);
-        stillRequest.addTarget(mTestRule.getReaderSurface());
+        previewBuilder.addTarget(mPreviewSurface);
+        stillBuilder.addTarget(mPreviewSurface);
+        stillBuilder.addTarget(mTestRule.getReaderSurface());
+        CaptureRequest previewRequest = previewBuilder.build();
+
+        configureAndSetCameraSession(outputSurfaces, useSessionKeys, previewRequest);
 
         // Start preview.
         mTestRule.getCameraSession().setRepeatingRequest(
-                previewRequest.build(), resultListener, mTestRule.getHandler());
+                previewRequest, resultListener, mTestRule.getHandler());
     }
 
     /**
@@ -1393,20 +1409,51 @@
 
     /**
      * Configure reader and preview outputs and wait until done.
+     *
+     * @return The preview capture request
      */
-    private void configureReaderAndPreviewOutputs() throws Exception {
+    private CaptureRequest configureReaderAndPreviewOutputs(
+            String id, boolean isColorOutputSupported)
+            throws Exception {
         if (mPreviewSurface == null || mTestRule.getReaderSurface() == null) {
             throw new IllegalStateException("preview and reader surface must be initilized first");
         }
-        mTestRule.setCameraSessionListener(new BlockingSessionCallback());
-        List<Surface> outputSurfaces = new ArrayList<>();
-        if (mTestRule.getStaticInfo().isColorOutputSupported()) {
-            outputSurfaces.add(mPreviewSurface);
+
+        // Create previewBuilder
+        CaptureRequest.Builder previewBuilder =
+                mTestRule.getCamera().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+        if (isColorOutputSupported) {
+            previewBuilder.addTarget(mPreviewSurface);
         }
+        previewBuilder.addTarget(mTestRule.getReaderSurface());
+
+
+        // Figure out constant target FPS range no larger than 30fps
+        CameraCharacteristics ch = mTestRule.getStaticInfo().getCharacteristics();
+        StreamConfigurationMap config =
+                ch.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        long minFrameDuration = Math.max(FRAME_DURATION_NS_30FPS,
+                config.getOutputMinFrameDuration(mImageReaderFormat, mPreviewSize));
+
+        List<Surface> outputSurfaces = new ArrayList<>();
         outputSurfaces.add(mTestRule.getReaderSurface());
-        mTestRule.setCameraSession(CameraTestUtils.configureCameraSession(
-                mTestRule.getCamera(), outputSurfaces,
-                mTestRule.getCameraSessionListener(), mTestRule.getHandler()));
+        if (isColorOutputSupported) {
+            outputSurfaces.add(mPreviewSurface);
+            minFrameDuration = Math.max(minFrameDuration,
+                    config.getOutputMinFrameDuration(SurfaceTexture.class, mPreviewSize));
+        }
+        Range<Integer> targetRange =
+                CameraTestUtils.getSuitableFpsRangeForDuration(id,
+                        minFrameDuration, mTestRule.getStaticInfo());
+        previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
+
+        // Create capture session
+        boolean useSessionKeys = isFpsRangeASessionKey(ch);
+        CaptureRequest previewRequest = previewBuilder.build();
+        mTestRule.setCameraSessionListener(new BlockingSessionCallback());
+        configureAndSetCameraSession(outputSurfaces, useSessionKeys, previewRequest);
+
+        return previewRequest;
     }
 
     /**
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index a05af2a..60eaebe 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -2571,6 +2571,26 @@
     }
 
     /**
+     * Check if rotate and crop is supported
+     */
+    public boolean isRotateAndCropSupported() {
+        int[] availableRotateAndCropModes = mCharacteristics.get(
+                CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES);
+
+        if (availableRotateAndCropModes == null) {
+            return false;
+        }
+
+        for (int mode : availableRotateAndCropModes) {
+            if (mode != CameraMetadata.SCALER_ROTATE_AND_CROP_NONE) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
      * Check if distortion correction is supported.
      */
     public boolean isDistortionCorrectionSupported() {
diff --git a/tests/contentcaptureservice/AndroidManifest.xml b/tests/contentcaptureservice/AndroidManifest.xml
index a4b456e..1ec33f9 100644
--- a/tests/contentcaptureservice/AndroidManifest.xml
+++ b/tests/contentcaptureservice/AndroidManifest.xml
@@ -14,116 +14,125 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.contentcaptureservice.cts"
-    android:targetSandboxVersion="2">
+     package="android.contentcaptureservice.cts"
+     android:targetSandboxVersion="2">
 
     <application>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name=".BlankActivity"
-                  android:label="Blank"
-                  android:taskAffinity=".BlankActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="Blank"
+             android:taskAffinity=".BlankActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".BlankWithTitleActivity"
-                  android:label="Blanka"
-                  android:taskAffinity=".BlankWithTitleActivity">
+             android:label="Blanka"
+             android:taskAffinity=".BlankWithTitleActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".LoginActivity"
-                  android:label="Login"
-                  android:taskAffinity=".LoginActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="Login"
+             android:taskAffinity=".LoginActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".ResizingEditActivity"
-                  android:label="ReizingEdit"
-                  android:taskAffinity=".ResizingEditActivity"
-                  android:windowSoftInputMode="adjustResize"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="ReizingEdit"
+             android:taskAffinity=".ResizingEditActivity"
+             android:windowSoftInputMode="adjustResize"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".ChildlessActivity"
-                  android:label="Childless"
-                  android:taskAffinity=".ChildlessActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="Childless"
+             android:taskAffinity=".ChildlessActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".CustomViewActivity"
-                  android:label="CustomView"
-                  android:taskAffinity=".CustomViewActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="CustomView"
+             android:taskAffinity=".CustomViewActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name=".OutOfProcessActivity"
-                  android:label="Oop"
-                  android:taskAffinity=".OutOfProcessActivity"
-                  android:theme="@android:style/Theme.NoTitleBar"
-                  android:process="android.contentcapture.cts.outside">
+             android:label="Oop"
+             android:taskAffinity=".OutOfProcessActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:process="android.contentcapture.cts.outside"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name=".DataSharingActivity"
-                  android:label="DataSharing"
-                  android:taskAffinity=".DataSharingActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="DataSharing"
+             android:taskAffinity=".DataSharingActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <receiver android:name=".SelfDestructReceiver"
-            android:exported="true"
-            android:process="android.contentcapture.cts.outside"/>
+             android:exported="true"
+             android:process="android.contentcapture.cts.outside"/>
 
-        <service
-            android:name=".CtsContentCaptureService"
-            android:label="CtsContentCaptureService"
-            android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE">
+        <service android:name=".CtsContentCaptureService"
+             android:label="CtsContentCaptureService"
+             android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.contentcapture.ContentCaptureService" />
+                <action android:name="android.service.contentcapture.ContentCaptureService"/>
             </intent-filter>
         </service>
 
@@ -137,10 +146,9 @@
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the AutoFill Framework APIs."
-        android:targetPackage="android.contentcaptureservice.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the AutoFill Framework APIs."
+         android:targetPackage="android.contentcaptureservice.cts">
     </instrumentation>
 
 </manifest>
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
index ea2fa41..164cdcf 100644
--- a/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
@@ -14,31 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.contentcaptureservice.cts2"
-          android:targetSandboxVersion="2">
+     package="android.contentcaptureservice.cts2"
+     android:targetSandboxVersion="2">
 
     <application>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name=".OutsideOfPackageActivity"
-                  android:label="OutsideOfPackage"
-                  android:taskAffinity=".OutsideOfPackageActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="OutsideOfPackage"
+             android:taskAffinity=".OutsideOfPackageActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the AutoFill Framework APIs."
-        android:targetPackage="android.contentcaptureservice.cts2" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the AutoFill Framework APIs."
+         android:targetPackage="android.contentcaptureservice.cts2">
     </instrumentation>
 
 </manifest>
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ContentCaptureConditionTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ContentCaptureConditionTest.java
index 0c9fd40..94c9234 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ContentCaptureConditionTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ContentCaptureConditionTest.java
@@ -47,7 +47,7 @@
         final ContentCaptureCondition condition =
                 new ContentCaptureCondition(mLocusId, FLAG_IS_REGEX);
         assertThat(condition).isNotNull();
-        assertThat(condition.getLocusId()).isSameAs(mLocusId);
-        assertThat(condition.getFlags()).isSameAs(FLAG_IS_REGEX);
+        assertThat(condition.getLocusId()).isSameInstanceAs(mLocusId);
+        assertThat(condition.getFlags()).isSameInstanceAs(FLAG_IS_REGEX);
     }
 }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ContentCaptureContextTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ContentCaptureContextTest.java
index 47bd20b..59c61c7 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ContentCaptureContextTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ContentCaptureContextTest.java
@@ -89,7 +89,7 @@
     @Test
     public void testSetGetBundle() {
         final Builder builder = mBuilder.setExtras(mExtras);
-        assertThat(builder).isSameAs(mBuilder);
+        assertThat(builder).isSameInstanceAs(mBuilder);
         final ContentCaptureContext context = builder.build();
         assertThat(context).isNotNull();
         assertExtras(context.getExtras());
@@ -99,7 +99,7 @@
     public void testParcel() {
         final Builder builder = mBuilder
                 .setExtras(mExtras);
-        assertThat(builder).isSameAs(mBuilder);
+        assertThat(builder).isSameInstanceAs(mBuilder);
         final ContentCaptureContext context = builder.build();
         assertEverything(context);
 
diff --git a/tests/contentsuggestions/AndroidManifest.xml b/tests/contentsuggestions/AndroidManifest.xml
index 0d47cc2..7786845 100644
--- a/tests/contentsuggestions/AndroidManifest.xml
+++ b/tests/contentsuggestions/AndroidManifest.xml
@@ -14,28 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.contentsuggestions.cts"
-    android:targetSandboxVersion="2">
+     package="android.contentsuggestions.cts"
+     android:targetSandboxVersion="2">
 
     <application>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <service
-            android:name=".CtsContentSuggestionsService"
-            android:label="CtsContentSuggestionsService"
-            android:permission="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE">
+        <service android:name=".CtsContentSuggestionsService"
+             android:label="CtsContentSuggestionsService"
+             android:permission="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.contentsuggestions.ContentSuggestionsService" />
+                <action android:name="android.service.contentsuggestions.ContentSuggestionsService"/>
             </intent-filter>
         </service>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the ContentSuggestionsManager APIs."
-        android:targetPackage="android.contentsuggestions.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the ContentSuggestionsManager APIs."
+         android:targetPackage="android.contentsuggestions.cts">
     </instrumentation>
 
 </manifest>
diff --git a/tests/contentsuggestions/TEST_MAPPING b/tests/contentsuggestions/TEST_MAPPING
new file mode 100644
index 0000000..aaf01a4
--- /dev/null
+++ b/tests/contentsuggestions/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsContentSuggestionsTestCases"
+    }
+  ]
+}
diff --git a/tests/contentsuggestions/src/android/contentsuggestions/cts/ContentSuggestionsManagerTest.java b/tests/contentsuggestions/src/android/contentsuggestions/cts/ContentSuggestionsManagerTest.java
index 31b595a..c927c63 100644
--- a/tests/contentsuggestions/src/android/contentsuggestions/cts/ContentSuggestionsManagerTest.java
+++ b/tests/contentsuggestions/src/android/contentsuggestions/cts/ContentSuggestionsManagerTest.java
@@ -19,7 +19,7 @@
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -32,6 +32,7 @@
 import android.app.contentsuggestions.ContentSuggestionsManager;
 import android.app.contentsuggestions.SelectionsRequest;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.util.Log;
 
@@ -48,6 +49,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.mockito.ArgumentCaptor;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -121,6 +124,26 @@
     }
 
     @Test
+    public void managerForwards_provideContextBitmap() {
+        int taskId = -1; // Explicit bitmap is provided; so task id is absent.
+
+        Bitmap expectedBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+        mManager.provideContextImage(expectedBitmap, new Bundle());
+        ArgumentCaptor<Bundle> bundleArg = ArgumentCaptor.forClass(Bundle.class);
+        ArgumentCaptor<Bitmap> bitmapArg = ArgumentCaptor.forClass(Bitmap.class);
+        verifyService().onProcessContextImage(eq(taskId), bitmapArg.capture(),
+            bundleArg.capture());
+        Bitmap actualBitmap = bundleArg.getValue().getParcelable(
+            ContentSuggestionsManager.EXTRA_BITMAP);
+
+        // Both the Bundle bitmap and the explicit bitmap should match the provided one.
+        assertThat(actualBitmap.getWidth()).isEqualTo(expectedBitmap.getWidth());
+        assertThat(actualBitmap.getHeight()).isEqualTo(expectedBitmap.getHeight());
+        assertThat(bitmapArg.getValue().getWidth()).isEqualTo(expectedBitmap.getWidth());
+        assertThat(bitmapArg.getValue().getHeight()).isEqualTo(expectedBitmap.getHeight());
+    }
+
+    @Test
     public void managerForwards_suggestContentSelections() {
         SelectionsRequest request = new SelectionsRequest.Builder(1).build();
         ContentSuggestionsManager.SelectionsCallback callback = (statusCode, selections) -> {};
diff --git a/tests/controls/AndroidManifest.xml b/tests/controls/AndroidManifest.xml
index 4ce024e..0862e62 100644
--- a/tests/controls/AndroidManifest.xml
+++ b/tests/controls/AndroidManifest.xml
@@ -15,21 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.controls.cts">
+     package="android.controls.cts">
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="CtsControlsDeviceActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="CtsControlsDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.controls.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.controls.cts">
     </instrumentation>
 </manifest>
diff --git a/tests/core/runner-axt/src/com/android/cts/runner/CtsTestRunListener.java b/tests/core/runner-axt/src/com/android/cts/runner/CtsTestRunListener.java
index db36213..0822ca2 100644
--- a/tests/core/runner-axt/src/com/android/cts/runner/CtsTestRunListener.java
+++ b/tests/core/runner-axt/src/com/android/cts/runner/CtsTestRunListener.java
@@ -41,7 +41,6 @@
 import java.net.Authenticator;
 import java.net.CookieHandler;
 import java.net.ResponseCache;
-import java.text.DateFormat;
 import java.util.Locale;
 import java.util.Properties;
 import java.util.TimeZone;
@@ -187,23 +186,80 @@
         }
     }
 
-    // http://code.google.com/p/vogar/source/browse/trunk/src/vogar/target/TestEnvironment.java
-    static class TestEnvironment {
-        private static final Field sDateFormatIs24HourField;
-        static {
+    private interface TestEnvironmentResetter {
+        Boolean getDateFormatIs24Hour();
+        void setDateFormatIs24Hour(Boolean value);
+        Properties createDefaultProperties();
+    }
+
+    private static class AndroidTestEnvironmentResetter implements TestEnvironmentResetter {
+        private final Field mDateFormatIs24HourField;
+
+        AndroidTestEnvironmentResetter() {
             try {
                 Class<?> dateFormatClass = Class.forName("java.text.DateFormat");
-                sDateFormatIs24HourField = dateFormatClass.getDeclaredField("is24Hour");
+                mDateFormatIs24HourField = dateFormatClass.getDeclaredField("is24Hour");
             } catch (ReflectiveOperationException e) {
                 throw new AssertionError("Missing DateFormat.is24Hour", e);
             }
         }
 
+        @Override
+        public Boolean getDateFormatIs24Hour() {
+            try {
+                return (Boolean) mDateFormatIs24HourField.get(null);
+            } catch (ReflectiveOperationException e) {
+                throw new AssertionError("Unable to get java.text.DateFormat.is24Hour", e);
+            }
+        }
+
+        @Override
+        public void setDateFormatIs24Hour(Boolean value) {
+            try {
+                mDateFormatIs24HourField.set(null, value);
+            } catch (ReflectiveOperationException e) {
+                throw new AssertionError("Unable to set java.text.DateFormat.is24Hour", e);
+            }
+        }
+
+        @Override
+        public Properties createDefaultProperties() {
+            return new Properties();
+        }
+    }
+
+    private static class StubTestEnvironmentResetter implements TestEnvironmentResetter {
+        @Override
+        public Boolean getDateFormatIs24Hour() {
+            return false;
+        }
+
+        @Override
+        public void setDateFormatIs24Hour(Boolean value) {
+        }
+
+        @Override
+        public Properties createDefaultProperties() {
+            return System.getProperties();
+        }
+    }
+
+    // http://code.google.com/p/vogar/source/browse/trunk/src/vogar/target/TestEnvironment.java
+    static class TestEnvironment {
+        private final static TestEnvironmentResetter sTestEnvironmentResetter;
+        static {
+            if (System.getProperty("java.vendor").toLowerCase().contains("android")) {
+                sTestEnvironmentResetter = new AndroidTestEnvironmentResetter();
+            } else {
+                sTestEnvironmentResetter = new StubTestEnvironmentResetter();
+            }
+        }
+
         private final Locale mDefaultLocale;
         private final TimeZone mDefaultTimeZone;
         private final HostnameVerifier mHostnameVerifier;
         private final SSLSocketFactory mSslSocketFactory;
-        private final Properties mProperties = new Properties();
+        private final Properties mProperties;
         private final Boolean mDefaultIs24Hour;
 
         TestEnvironment(Context context) {
@@ -212,6 +268,7 @@
             mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
             mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
 
+            mProperties = sTestEnvironmentResetter.createDefaultProperties();
             mProperties.setProperty("user.home", "");
             mProperties.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());
             // The CDD mandates that devices that support WiFi are the only ones that will have
@@ -219,7 +276,7 @@
             PackageManager pm = context.getPackageManager();
             mProperties.setProperty("android.cts.device.multicast",
                     Boolean.toString(pm.hasSystemFeature(PackageManager.FEATURE_WIFI)));
-            mDefaultIs24Hour = getDateFormatIs24Hour();
+            mDefaultIs24Hour = sTestEnvironmentResetter.getDateFormatIs24Hour();
 
             // There are tests in libcore that should be disabled for low ram devices. They can't
             // access ActivityManager to call isLowRamDevice, but can read system properties.
@@ -239,24 +296,7 @@
             ResponseCache.setDefault(null);
             HttpsURLConnection.setDefaultHostnameVerifier(mHostnameVerifier);
             HttpsURLConnection.setDefaultSSLSocketFactory(mSslSocketFactory);
-            setDateFormatIs24Hour(mDefaultIs24Hour);
-        }
-
-        private static Boolean getDateFormatIs24Hour() {
-            try {
-                return (Boolean) sDateFormatIs24HourField.get(null);
-            } catch (ReflectiveOperationException e) {
-                throw new AssertionError("Unable to get java.text.DateFormat.is24Hour", e);
-            }
-        }
-
-        private static void setDateFormatIs24Hour(Boolean value) {
-            try {
-                sDateFormatIs24HourField.set(null, value);
-            } catch (ReflectiveOperationException e) {
-                throw new AssertionError("Unable to set java.text.DateFormat.is24Hour", e);
-            }
+            sTestEnvironmentResetter.setDateFormatIs24Hour(mDefaultIs24Hour);
         }
     }
-
 }
diff --git a/tests/devicepolicy/Android.bp b/tests/devicepolicy/Android.bp
new file mode 100644
index 0000000..d556cc7
--- /dev/null
+++ b/tests/devicepolicy/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsDevicePolicyTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+        "androidx.test.ext.junit",
+        "testng", // used for assertThrows
+        // TODO: Remove this once we remove ui automator usage
+        "androidx.test.uiautomator_uiautomator",
+    ],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    test_options: {
+    extra_test_configs: ["DevicePolicyWorkProfileTest.xml"]
+    },
+    sdk_version: "test_current",
+}
diff --git a/tests/devicepolicy/AndroidManifest.xml b/tests/devicepolicy/AndroidManifest.xml
new file mode 100644
index 0000000..a809d68
--- /dev/null
+++ b/tests/devicepolicy/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.devicepolicy.cts"
+          android:targetSandboxVersion="2">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name=".MainActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".NonMainActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="nonMainActivity"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".NonExportedActivity"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.devicepolicy.cts"
+                     android:label="CTS tests for device policy" />
+</manifest>
diff --git a/tests/devicepolicy/AndroidTest.xml b/tests/devicepolicy/AndroidTest.xml
new file mode 100644
index 0000000..23ca675
--- /dev/null
+++ b/tests/devicepolicy/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Device Policy test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
+         here are not applicable -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.devicepolicy.cts" />
+        <option name="exclude-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
+        <option name="exclude-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/devicepolicy/DevicePolicySecondaryUserTest.xml b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
new file mode 100644
index 0000000..8ea32bc
--- /dev/null
+++ b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Config for CTS Device Policy test cases on a secondary user">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
+         here are not applicable -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer">
+        <option name="test-package-name" value="android.devicepolicy.cts" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.devicepolicy.cts" />
+        <option name="include-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser" />
+        <!--        <option name="instrumentation-arg" key="skip-test-teardown" value="true" />-->
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/devicepolicy/DevicePolicyWorkProfileTest.xml b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
new file mode 100644
index 0000000..951511d
--- /dev/null
+++ b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Device Policy test cases on a work profile">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
+         here are not applicable -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer">
+        <option name="test-package-name" value="android.devicepolicy.cts" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.devicepolicy.cts" />
+        <option name="include-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
+<!--        <option name="instrumentation-arg" key="skip-test-teardown" value="true" />-->
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/devicepolicy/OWNERS b/tests/devicepolicy/OWNERS
new file mode 100644
index 0000000..b37176e
--- /dev/null
+++ b/tests/devicepolicy/OWNERS
@@ -0,0 +1,7 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
+alexkershaw@google.com
+eranm@google.com
+rubinxu@google.com
+sandness@google.com
+pgrafov@google.com
+scottjonathan@google.com
diff --git a/tests/devicepolicy/res/layout/main.xml b/tests/devicepolicy/res/layout/main.xml
new file mode 100644
index 0000000..af2bb77
--- /dev/null
+++ b/tests/devicepolicy/res/layout/main.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/user_textview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+    />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
new file mode 100644
index 0000000..79c65de
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.devicepolicy.cts;
+
+import static com.android.compatibility.common.util.enterprise.DeviceState.UserType.PRIMARY_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.CrossProfileApps;
+import android.os.UserHandle;
+import android.os.UserManager;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.compatibility.common.util.enterprise.Preconditions;
+import com.android.compatibility.common.util.enterprise.annotations.EnsureHasSecondaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.EnsureHasWorkProfile;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile;
+
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class CrossProfileAppsTest {
+
+    private static final String ID_USER_TEXTVIEW =
+            "com.android.cts.devicepolicy:id/user_textview";
+    private static final long TIMEOUT_WAIT_UI = TimeUnit.SECONDS.toMillis(10);
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+    private static final CrossProfileApps sCrossProfileApps =
+            sContext.getSystemService(CrossProfileApps.class);
+    private static final UserManager sUserManager = sContext.getSystemService(UserManager.class);
+
+    @ClassRule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    @Rule
+    public final Preconditions mPreconditions = new Preconditions(sDeviceState);
+
+    @Test
+    @RequireRunOnPrimaryUser
+    public void getTargetUserProfiles_callingFromPrimaryUser_doesNotContainPrimaryUser() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).doesNotContain(sDeviceState.getPrimaryUser());
+    }
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser
+    public void getTargetUserProfiles_callingFromPrimaryUser_doesNotContainSecondaryUser() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).doesNotContain(sDeviceState.getSecondaryUser());
+    }
+
+    @Test
+    @RequireRunOnWorkProfile
+    public void getTargetUserProfiles_callingFromWorkProfile_containsPrimaryUser() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).contains(sDeviceState.getPrimaryUser());
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void getTargetUserProfiles_callingFromPrimaryUser_containsWorkProfile() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).contains(sDeviceState.getWorkProfile());
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile(installTestApp = false)
+    public void getTargetUserProfiles_callingFromPrimaryUser_appNotInstalledInWorkProfile_doesNotContainWorkProfile() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).doesNotContain(sDeviceState.getWorkProfile());
+    }
+
+    @Test
+    @RequireRunOnSecondaryUser
+    @EnsureHasWorkProfile(forUser = PRIMARY_USER)
+    public void getTargetUserProfiles_callingFromSecondaryUser_doesNotContainWorkProfile() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).doesNotContain(
+                sDeviceState.getWorkProfile(/* forUser= */ PRIMARY_USER));
+    }
+
+    @Test
+    @RequireRunOnWorkProfile
+    @Ignore // TODO(scottjonathan): Replace use of UIAutomator
+    public void startMainActivity_callingFromWorkProfile_targetIsPrimaryUser_launches() {
+        sCrossProfileApps.startMainActivity(
+                new ComponentName(sContext, MainActivity.class), sDeviceState.getPrimaryUser());
+
+        assertMainActivityLaunchedForUser(sDeviceState.getPrimaryUser());
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    @Ignore // TODO(scottjonathan): Replace use of UIAutomator
+    public void startMainActivity_callingFromPrimaryUser_targetIsWorkProfile_launches() {
+        sCrossProfileApps.startMainActivity(
+                new ComponentName(sContext, MainActivity.class), sDeviceState.getWorkProfile());
+
+        assertMainActivityLaunchedForUser(sDeviceState.getWorkProfile());
+    }
+
+    private void assertMainActivityLaunchedForUser(UserHandle user) {
+        // TODO(scottjonathan): Replace this with a standard event log or similar to avoid UI
+        // Look for the text view to verify that MainActivity is started.
+        UiObject2 textView = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+                .wait(
+                        Until.findObject(By.res(ID_USER_TEXTVIEW)),
+                        TIMEOUT_WAIT_UI);
+        assertNotNull("Failed to start activity in target user", textView);
+        // Look for the text in textview, it should be the serial number of target user.
+        assertEquals("Activity is started in wrong user",
+                String.valueOf(sUserManager.getSerialNumberForUser(user)),
+                textView.getText());
+    }
+
+    @Test
+    public void startMainActivity_activityNotExported_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.startMainActivity(
+                    new ComponentName(sContext, NonExportedActivity.class),
+                    sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    public void startMainActivity_activityNotMain_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.startMainActivity(
+                    new ComponentName(sContext, NonMainActivity.class),
+                    sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    @Ignore // TODO(scottjonathan): This requires another app to be installed which can be launched
+    public void startMainActivity_activityIncorrectPackage_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+
+        });
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    public void
+            startMainActivity_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.startMainActivity(
+                    new ComponentName(sContext, MainActivity.class), sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser
+    public void
+    startMainActivity_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.startMainActivity(
+                    new ComponentName(sContext, MainActivity.class),
+                    sDeviceState.getSecondaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnSecondaryUser
+    @EnsureHasWorkProfile(forUser = PRIMARY_USER)
+    public void
+    startMainActivity_callingFromSecondaryUser_targetIsWorkProfile_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.startMainActivity(
+                    new ComponentName(sContext, MainActivity.class),
+                    sDeviceState.getWorkProfile(/* forUser= */ PRIMARY_USER));
+        });
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingLabel(sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser
+    public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingLabel(sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnSecondaryUser
+    @EnsureHasWorkProfile(forUser = PRIMARY_USER)
+    public void getProfileSwitchingLabel_callingFromSecondaryUser_targetIsWorkProfile_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingLabel(
+                    sDeviceState.getWorkProfile(/* forUser= */ PRIMARY_USER));
+        });
+    }
+
+    @Test
+    @RequireRunOnWorkProfile
+    public void getProfileSwitchingLabel_callingFromWorProfile_targetIsPrimaryUser_notNull() {
+        assertThat(sCrossProfileApps.getProfileSwitchingLabel(
+                sDeviceState.getPrimaryUser())).isNotNull();
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsWorkProfile_notNull() {
+        assertThat(sCrossProfileApps.getProfileSwitchingLabel(
+                sDeviceState.getWorkProfile())).isNotNull();
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    public void getProfileSwitchingLabelIconDrawable_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingIconDrawable(sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser
+    public void getProfileSwitchingLabelIconDrawable_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingIconDrawable(sDeviceState.getSecondaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnSecondaryUser
+    @EnsureHasWorkProfile(forUser = PRIMARY_USER)
+    public void getProfileSwitchingLabelIconDrawable_callingFromSecondaryUser_targetIsWorkProfile_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingIconDrawable(
+                    sDeviceState.getWorkProfile(/* forUser= */ PRIMARY_USER));
+        });
+    }
+
+    @Test
+    @RequireRunOnWorkProfile
+    public void getProfileSwitchingIconDrawable_callingFromWorkProfile_targetIsPrimaryUser_notNull() {
+        assertThat(sCrossProfileApps.getProfileSwitchingIconDrawable(
+                sDeviceState.getPrimaryUser())).isNotNull();
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void getProfileSwitchingIconDrawable_callingFromPrimaryUser_targetIsWorkProfile_notNull() {
+        assertThat(sCrossProfileApps.getProfileSwitchingIconDrawable(
+                sDeviceState.getWorkProfile())).isNotNull();
+    }
+}
\ No newline at end of file
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/MainActivity.java b/tests/devicepolicy/src/android/devicepolicy/cts/MainActivity.java
new file mode 100644
index 0000000..ebfaf40
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/MainActivity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.devicepolicy.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserManager;
+import android.widget.TextView;
+
+/**
+ * An activity that displays the serial number of the user that it is running into.
+ */
+public class MainActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        TextView textView = findViewById(R.id.user_textview);
+        textView.setText(Long.toString(getCurrentUserSerialNumber()));
+    }
+
+    private long getCurrentUserSerialNumber() {
+        UserManager userManager = getSystemService(UserManager.class);
+        return userManager.getSerialNumberForUser(Process.myUserHandle());
+    }
+}
\ No newline at end of file
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NonExportedActivity.java b/tests/devicepolicy/src/android/devicepolicy/cts/NonExportedActivity.java
new file mode 100644
index 0000000..a76f4ee
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NonExportedActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.devicepolicy.cts;
+
+import android.app.Activity;
+
+/** Activity used for Cross Profile Apps Tests */
+public class NonExportedActivity extends Activity {
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NonMainActivity.java b/tests/devicepolicy/src/android/devicepolicy/cts/NonMainActivity.java
new file mode 100644
index 0000000..7ef5f8a
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NonMainActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.devicepolicy.cts;
+
+import android.app.Activity;
+
+/** Activity used for Cross Profile Apps Tests */
+public class NonMainActivity extends Activity {
+}
diff --git a/tests/fragment/AndroidManifest.xml b/tests/fragment/AndroidManifest.xml
index 447f9a8..9a16a60 100644
--- a/tests/fragment/AndroidManifest.xml
+++ b/tests/fragment/AndroidManifest.xml
@@ -13,29 +13,31 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.fragment.cts"
-    android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.fragment.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name=".FragmentTestActivity">
+        <activity android:name=".FragmentTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".LoaderActivity"/>
-        <activity android:name=".NewIntentActivity" android:launchMode="singleInstance" />
+        <activity android:name=".NewIntentActivity"
+             android:launchMode="singleInstance"/>
         <activity android:name=".ConfigOnStopActivity"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.fragment.cts"
-                     android:label="CTS tests of android.app Fragments" />
+         android:targetPackage="android.fragment.cts"
+         android:label="CTS tests of android.app Fragments"/>
 
 </manifest>
-
diff --git a/tests/framework/base/windowmanager/Android.mk b/tests/framework/base/windowmanager/Android.mk
index 8a7f090..8caaa78 100644
--- a/tests/framework/base/windowmanager/Android.mk
+++ b/tests/framework/base/windowmanager/Android.mk
@@ -43,6 +43,7 @@
     CtsSurfaceValidatorLib \
     CtsMockInputMethodLib \
     metrics-helper-lib \
+    truth-prebuilt \
 
 LOCAL_COMPATIBILITY_SUITE := cts general-tests
 
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index aabcc6b..1945147 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -16,291 +16,261 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          package="android.server.wm.cts"
-          android:targetSandboxVersion="2">
+     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+     package="android.server.wm.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.READ_LOGS" />
-    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
-    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.STOP_APP_SWITCHES"/>
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
     <application android:label="CtsWindowManagerDeviceTestCases"
-            android:requestLegacyExternalStorage="true">
+         android:requestLegacyExternalStorage="true">
         <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MaxAspectRatioActivity"
-            android:label="MaxAspectRatioActivity"
-            android:maxAspectRatio="1.0"
-            android:resizeableActivity="false" />
+        <activity android:name="android.server.wm.AspectRatioTests$MaxAspectRatioActivity"
+             android:label="MaxAspectRatioActivity"
+             android:maxAspectRatio="1.0"
+             android:resizeableActivity="false"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MetaDataMaxAspectRatioActivity"
-            android:label="MetaDataMaxAspectRatioActivity"
-            android:resizeableActivity="false">
-            <meta-data
-                android:name="android.max_aspect"
-                android:value="1.0" />
+        <activity android:name="android.server.wm.AspectRatioTests$MetaDataMaxAspectRatioActivity"
+             android:label="MetaDataMaxAspectRatioActivity"
+             android:resizeableActivity="false">
+            <meta-data android:name="android.max_aspect"
+                 android:value="1.0"/>
         </activity>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MaxAspectRatioResizeableActivity"
-            android:label="MaxAspectRatioResizeableActivity"
-            android:maxAspectRatio="1.0"
-            android:resizeableActivity="true" />
+        <activity android:name="android.server.wm.AspectRatioTests$MaxAspectRatioResizeableActivity"
+             android:label="MaxAspectRatioResizeableActivity"
+             android:maxAspectRatio="1.0"
+             android:resizeableActivity="true"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MaxAspectRatioUnsetActivity"
-            android:label="MaxAspectRatioUnsetActivity"
-            android:resizeableActivity="false" />
+        <activity android:name="android.server.wm.AspectRatioTests$MaxAspectRatioUnsetActivity"
+             android:label="MaxAspectRatioUnsetActivity"
+             android:resizeableActivity="false"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MinAspectRatioActivity"
-            android:label="MinAspectRatioActivity"
-            android:minWidth="1dp"
-            android:minAspectRatio="3.0"
-            android:resizeableActivity="false" />
+        <activity android:name="android.server.wm.AspectRatioTests$MinAspectRatioActivity"
+             android:label="MinAspectRatioActivity"
+             android:minWidth="1dp"
+             android:minAspectRatio="3.0"
+             android:resizeableActivity="false"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MinAspectRatioResizeableActivity"
-            android:label="MinAspectRatioResizeableActivity"
-            android:minWidth="1dp"
-            android:minAspectRatio="3.0"
-            android:resizeableActivity="true" />
+        <activity android:name="android.server.wm.AspectRatioTests$MinAspectRatioResizeableActivity"
+             android:label="MinAspectRatioResizeableActivity"
+             android:minWidth="1dp"
+             android:minAspectRatio="3.0"
+             android:resizeableActivity="true"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MinAspectRatioUnsetActivity"
-            android:label="MinAspectRatioUnsetActivity"
-            android:resizeableActivity="false" />
+        <activity android:name="android.server.wm.AspectRatioTests$MinAspectRatioUnsetActivity"
+             android:label="MinAspectRatioUnsetActivity"
+             android:resizeableActivity="false"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MinAspectRatioLandscapeActivity"
-            android:label="MinAspectRatioLandscapeActivity"
-            android:minWidth="1dp"
-            android:minAspectRatio="3.0"
-            android:resizeableActivity="false"
-            android:screenOrientation="landscape" />
+        <activity android:name="android.server.wm.AspectRatioTests$MinAspectRatioLandscapeActivity"
+             android:label="MinAspectRatioLandscapeActivity"
+             android:minWidth="1dp"
+             android:minAspectRatio="3.0"
+             android:resizeableActivity="false"
+             android:screenOrientation="landscape"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MinAspectRatioPortraitActivity"
-            android:label="MinAspectRatioPortraitActivity"
-            android:minWidth="1dp"
-            android:minAspectRatio="3.0"
-            android:resizeableActivity="false"
-            android:screenOrientation="portrait" />
+        <activity android:name="android.server.wm.AspectRatioTests$MinAspectRatioPortraitActivity"
+             android:label="MinAspectRatioPortraitActivity"
+             android:minWidth="1dp"
+             android:minAspectRatio="3.0"
+             android:resizeableActivity="false"
+             android:screenOrientation="portrait"/>
 
         <activity android:name="android.server.wm.ActivityManagerTestBase$SideActivity"
-                  android:resizeableActivity="true"
-                  android:taskAffinity="nobody.but.SideActivity"/>
+             android:resizeableActivity="true"
+             android:taskAffinity="nobody.but.SideActivity"/>
 
         <activity android:name="android.server.wm.ActivityManagerTestBase$ConfigChangeHandlingActivity"
-            android:resizeableActivity="true"
-            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen" />
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/>
 
-        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$FirstActivity" />
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$FirstActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$ThirdActivity"/>
 
-        <activity
-            android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TranslucentActivity"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TranslucentActivity"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
 
-        <activity
-            android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondTranslucentActivity"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondTranslucentActivity"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$CallbackTrackingActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondCallbackTrackingActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TranslucentCallbackTrackingActivity"
-                  android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
 
-        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$ShowWhenLockedCallbackTrackingActivity" />
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$ShowWhenLockedCallbackTrackingActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondProcessCallbackTrackingActivity"
-                  android:process=":SecondProcess"
-                  android:exported="true"/>
+             android:process=":SecondProcess"
+             android:exported="true"/>
 
         <provider android:name="android.server.wm.lifecycle.LifecycleLog"
-                  android:authorities="android.server.wm.lifecycle.logprovider"
-                  android:exported="true" />
+             android:authorities="android.server.wm.lifecycle.logprovider"
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$LaunchForResultActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$ResultActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SingleTopActivity"
-                  android:launchMode="singleTop" />
+             android:launchMode="singleTop"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$ConfigChangeHandlingActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density" />
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$PipActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:supportsPictureInPicture="true"/>
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:supportsPictureInPicture="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$AlwaysFocusablePipActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  androidprv:alwaysFocusable="true"
-                  android:exported="true"/>
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             androidprv:alwaysFocusable="true"
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SlowActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$NoDisplayActivity"
-                  android:theme="@android:style/Theme.NoDisplay" />
+             android:theme="@android:style/Theme.NoDisplay"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$DifferentAffinityActivity"
-                  android:taskAffinity="nobody.but.DifferentAffinityActivity" />
+             android:taskAffinity="nobody.but.DifferentAffinityActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TransitionSourceActivity"
-                  android:theme="@style/window_activity_transitions" />
+             android:theme="@style/window_activity_transitions"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TransitionDestinationActivity"
-                  android:theme="@style/window_activity_transitions" />
+             android:theme="@style/window_activity_transitions"/>
 
         <activity android:name="android.server.wm.MultiDisplayActivityLaunchTests$ImmediateLaunchTestActivity"
-                  android:allowEmbedded="true" />
+             android:allowEmbedded="true"/>
 
         <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen" />
-        <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity2" />
-        <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivityWithBrokenContextWrapper" />
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/>
+        <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity2"/>
+        <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivityWithBrokenContextWrapper"/>
 
-        <activity android:name="android.server.wm.MultiDisplayClientTests$ClientTestActivity" />
+        <activity android:name="android.server.wm.MultiDisplayClientTests$ClientTestActivity"/>
         <activity android:name="android.server.wm.MultiDisplayClientTests$NoRelaunchActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
-        />
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/>
 
-        <activity android:name="android.server.wm.KeyguardLockedTests$ShowImeAfterLockscreenActivity" />
+        <activity android:name="android.server.wm.KeyguardLockedTests$ShowImeAfterLockscreenActivity"/>
 
-        <activity android:name="android.server.wm.KeyguardLockedTests$ShowWhenLockedImeActivity" />
+        <activity android:name="android.server.wm.KeyguardLockedTests$ShowWhenLockedImeActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$StandardActivity"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SecondStandardActivity"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$StandardWithSingleTopActivity"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SingleTopActivity"
-                  android:launchMode="singleTop"
-                  android:exported="true" />
+             android:launchMode="singleTop"
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SingleInstanceActivity"
-                  android:launchMode="singleInstance"
-                  android:exported="true" />
+             android:launchMode="singleInstance"
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SingleTaskActivity"
-                  android:launchMode="singleTask"
-                  android:exported="true" />
+             android:launchMode="singleTask"
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$TestLaunchingActivity"
-                  android:taskAffinity="nobody.but.LaunchingActivity"
-                  android:exported="true" />
+             android:taskAffinity="nobody.but.LaunchingActivity"
+             android:exported="true"/>
 
-        <activity
-            android:name="android.server.wm.lifecycle.ActivityStarterTests$LaunchingAndFinishActivity"
-            android:taskAffinity="nobody.but.LaunchingActivity"
-            android:exported="true"/>
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$LaunchingAndFinishActivity"
+             android:taskAffinity="nobody.but.LaunchingActivity"
+             android:exported="true"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$ClearTaskOnLaunchActivity"
+                  android:clearTaskOnLaunch="true"/>
 
         <activity android:name="android.server.wm.ActivityViewTest$ActivityViewTestActivity"
-                  android:configChanges="keyboardHidden"
-                  android:exported="true"/>
+             android:configChanges="keyboardHidden"
+             android:exported="true"/>
 
-        <provider
-            android:name="android.server.wm.TestJournalProvider"
-            android:authorities="android.server.wm.testjournalprovider"
-            android:exported="true" />
+        <provider android:name="android.server.wm.TestJournalProvider"
+             android:authorities="android.server.wm.testjournalprovider"
+             android:exported="true"/>
 
         <!--intent tests-->
         <activity android:name="android.server.wm.intent.Activities$RegularActivity"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$SingleTopActivity"
-            android:launchMode="singleTop"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$SingleInstanceActivity"
-            android:launchMode="singleInstance"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$SingleInstanceActivity2"
-            android:launchMode="singleInstance"
-            android:taskAffinity=".t1"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$SingleTaskActivity"
-            android:launchMode="singleTask"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$SingleTaskActivity2"
-            android:launchMode="singleTask"
-            android:taskAffinity=".t1"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$TaskAffinity1Activity"
-            android:allowTaskReparenting="true"
-            android:launchMode="standard"
-            android:taskAffinity=".t1"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$TaskAffinity1Activity2"
-            android:allowTaskReparenting="true"
-            android:launchMode="standard"
-            android:taskAffinity=".t1"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
-            android:relinquishTaskIdentity="true"
-            android:taskAffinity=".t1"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$TaskAffinity2Activity"
-            android:allowTaskReparenting="true"
-            android:launchMode="standard"
-            android:taskAffinity=".t2"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$TaskAffinity3Activity"
-            android:allowTaskReparenting="true"
-            android:launchMode="standard"
-            android:taskAffinity=".t3"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$ClearTaskOnLaunchActivity"
-            android:allowTaskReparenting="true"
-            android:clearTaskOnLaunch="true"
-            android:launchMode="standard"
-            android:taskAffinity=".t2"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$DocumentLaunchIntoActivity"
-            android:documentLaunchMode="intoExisting"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$DocumentLaunchAlwaysActivity"
-            android:documentLaunchMode="always"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$DocumentLaunchNeverActivity"
-            android:documentLaunchMode="never"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$NoHistoryActivity"
-            android:noHistory="true"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$LauncherActivity"
-            android:documentLaunchMode="always"
-            android:launchMode="singleInstance"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$RelinquishTaskIdentityActivity"
-            android:relinquishTaskIdentity="true"/>
+        <activity android:name="android.server.wm.intent.Activities$SingleTopActivity"
+             android:launchMode="singleTop"/>
+        <activity android:name="android.server.wm.intent.Activities$SingleInstanceActivity"
+             android:launchMode="singleInstance"/>
+        <activity android:name="android.server.wm.intent.Activities$SingleInstanceActivity2"
+             android:launchMode="singleInstance"
+             android:taskAffinity=".t1"/>
+        <activity android:name="android.server.wm.intent.Activities$SingleTaskActivity"
+             android:launchMode="singleTask"/>
+        <activity android:name="android.server.wm.intent.Activities$SingleTaskActivity2"
+             android:launchMode="singleTask"
+             android:taskAffinity=".t1"/>
+        <activity android:name="android.server.wm.intent.Activities$TaskAffinity1Activity"
+             android:allowTaskReparenting="true"
+             android:launchMode="standard"
+             android:taskAffinity=".t1"/>
+        <activity android:name="android.server.wm.intent.Activities$TaskAffinity1Activity2"
+             android:allowTaskReparenting="true"
+             android:launchMode="standard"
+             android:taskAffinity=".t1"/>
+        <activity android:name="android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
+             android:relinquishTaskIdentity="true"
+             android:taskAffinity=".t1"/>
+        <activity android:name="android.server.wm.intent.Activities$TaskAffinity2Activity"
+             android:allowTaskReparenting="true"
+             android:launchMode="standard"
+             android:taskAffinity=".t2"/>
+        <activity android:name="android.server.wm.intent.Activities$TaskAffinity3Activity"
+             android:allowTaskReparenting="true"
+             android:launchMode="standard"
+             android:taskAffinity=".t3"/>
+        <activity android:name="android.server.wm.intent.Activities$ClearTaskOnLaunchActivity"
+             android:allowTaskReparenting="true"
+             android:clearTaskOnLaunch="true"
+             android:launchMode="standard"
+             android:taskAffinity=".t2"/>
+        <activity android:name="android.server.wm.intent.Activities$DocumentLaunchIntoActivity"
+             android:documentLaunchMode="intoExisting"/>
+        <activity android:name="android.server.wm.intent.Activities$DocumentLaunchAlwaysActivity"
+             android:documentLaunchMode="always"/>
+        <activity android:name="android.server.wm.intent.Activities$DocumentLaunchNeverActivity"
+             android:documentLaunchMode="never"/>
+        <activity android:name="android.server.wm.intent.Activities$NoHistoryActivity"
+             android:noHistory="true"/>
+        <activity android:name="android.server.wm.intent.Activities$LauncherActivity"
+             android:documentLaunchMode="always"
+             android:launchMode="singleInstance"/>
+        <activity android:name="android.server.wm.intent.Activities$RelinquishTaskIdentityActivity"
+             android:relinquishTaskIdentity="true"/>
 
-        <service
-            android:name="android.server.wm.TestLogService"
-            android:enabled="true"
-            android:exported="true">
+        <service android:name="android.server.wm.TestLogService"
+             android:enabled="true"
+             android:exported="true">
         </service>
 
         <activity android:name="android.server.wm.AlertWindowsAppOpsTestsActivity"/>
@@ -313,119 +283,132 @@
                   android:showWhenLocked="true"/>
 
         <activity android:name="android.server.wm.WindowInsetsAnimationSynchronicityTests$TestActivity"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"/>
-        <service
-            android:name="android.server.wm.WindowInsetsAnimationSynchronicityTests$SimpleIme"
-            android:label="Simple IME"
-            android:permission="android.permission.BIND_INPUT_METHOD">
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"/>
+        <service android:name="android.server.wm.WindowInsetsAnimationSynchronicityTests$SimpleIme"
+             android:label="Simple IME"
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
-            <meta-data
-                android:name="android.view.im"
-                android:resource="@xml/simple_method" />
+            <meta-data android:name="android.view.im"
+                 android:resource="@xml/simple_method"/>
         </service>
 
         <activity android:name="android.server.wm.KeyEventActivity"
-                  android:exported="true"
-                  android:configChanges="orientation|screenLayout"
-                  android:showWhenLocked="true"
-        />
+             android:exported="true"
+             android:configChanges="orientation|screenLayout"
+             android:showWhenLocked="true"/>
         <activity android:name="android.server.wm.WindowInsetsPolicyTest$TestActivity"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"/>
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"/>
         <activity android:name="android.server.wm.WindowInsetsPolicyTest$FullscreenTestActivity"/>
         <activity android:name="android.server.wm.WindowInsetsPolicyTest$FullscreenWmFlagsTestActivity"/>
         <activity android:name="android.server.wm.WindowInsetsPolicyTest$ImmersiveFullscreenTestActivity"
-                  android:documentLaunchMode="always"
-                  android:theme="@style/no_animation" />
+             android:documentLaunchMode="always"
+             android:theme="@style/no_animation"/>
         <activity android:name="android.server.wm.LayoutTests$TestActivity"
-                  android:theme="@style/no_animation" />
+             android:theme="@style/no_animation"/>
         <activity android:name="android.server.wm.LocationOnScreenTests$TestActivity"
-                  android:theme="@style/no_starting_window" />
-        <activity android:name="android.server.wm.LocationInWindowTests$TestActivity" />
+             android:theme="@style/no_starting_window"/>
+        <activity android:name="android.server.wm.LocationInWindowTests$TestActivity"/>
         <activity android:name="android.server.wm.EnsureBarContrastTest$TestActivity"
-                  android:theme="@style/no_starting_window" />
-        <activity android:name="android.server.wm.WindowFocusTests$PrimaryActivity" />
+             android:theme="@style/no_starting_window"/>
+        <activity android:name="android.server.wm.WindowFocusTests$PrimaryActivity"/>
         <activity android:name="android.server.wm.WindowFocusTests$SecondaryActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density" />
-        <activity android:name="android.server.wm.WindowFocusTests$LosingFocusActivity" />
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"/>
+        <activity android:name="android.server.wm.WindowFocusTests$LosingFocusActivity"/>
+        <activity android:name="android.server.wm.WindowFocusTests$AutoEngagePointerCaptureActivity" />
         <activity android:name="android.server.wm.WindowMetricsTests$MetricsActivity"
-                  android:exported="true" />
+             android:exported="true"/>
         <activity android:name="android.app.Activity"/>
-        <activity android:name="android.server.wm.WindowInsetsLayoutTests$TestActivity" />
-        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestActivity" />
-        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestHideOnCreateActivity" />
-        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestShowOnCreateActivity" />
+        <activity android:name="android.server.wm.WindowInsetsLayoutTests$TestActivity"/>
+        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestActivity"/>
+        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestHideOnCreateActivity"/>
+        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestShowOnCreateActivity"/>
 
         <activity android:name="android.server.wm.DragDropTest$DragDropActivity"
-                  android:screenOrientation="locked"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:label="DragDropActivity">
+             android:screenOrientation="locked"
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"
+             android:label="DragDropActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="android.server.wm.DecorInsetTestsBase$TestActivity"
-            android:label="DecorInsetTestsBase.TestActivity"
-            android:exported="true" />
+        <activity android:name="android.server.wm.DecorInsetTestsBase$TestActivity"
+             android:label="DecorInsetTestsBase.TestActivity"
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.WindowCtsActivity"
-                  android:theme="@android:style/Theme.Material.NoActionBar"
-                  android:screenOrientation="locked"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
-                  android:label="WindowCtsActivity">
+             android:theme="@android:style/Theme.Material.NoActionBar"
+             android:screenOrientation="locked"
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"
+             android:label="WindowCtsActivity"
+             android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.server.wm.SurfaceViewCtsActivity"
-                  android:screenOrientation="locked"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:label="SurfaceViewCtsActivity">
+             android:screenOrientation="locked"
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"
+             android:label="SurfaceViewCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"
-                  android:screenOrientation="locked"
-                  android:theme="@style/WhiteBackgroundTheme">
+             android:screenOrientation="locked"
+             android:theme="@style/WhiteBackgroundTheme"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.server.wm.WindowInputTests$TestActivity" />
 
         <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
-                 android:foregroundServiceType="mediaProjection"
-                 android:enabled="true">
+             android:foregroundServiceType="mediaProjection"
+             android:enabled="true">
         </service>
 
         <activity android:name="android.server.wm.StartActivityAsUserActivity"
-                  android:directBootAware="true"/>
+             android:directBootAware="true"/>
 
         <activity android:name="android.server.wm.WindowInsetsAnimationTestBase$TestActivity"
-                  android:theme="@android:style/Theme.Material.NoActionBar" />
+             android:theme="@android:style/Theme.Material.NoActionBar"/>
 
         <activity android:name="android.server.wm.ForceRelayoutTestBase$TestActivity"
-                  android:exported="true" />
+             android:exported="true"/>
+
+        <activity android:name="android.server.wm.ActivityTransitionTests$LauncherActivity"/>
+
+        <activity android:name="android.server.wm.ActivityTransitionTests$TransitionActivity"/>
+
+        <activity android:name="android.server.wm.WindowUntrustedTouchTest$TestActivity"
+                  android:exported="true"/>
+
+        <activity android:name="android.server.wm.WindowUntrustedTouchTest$OverlayActivity"
+                  android:exported="true"
+                  android:theme="@android:style/Theme.Translucent"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.server.wm.cts"
-                     android:label="CTS tests of WindowManager">
+         android:targetPackage="android.server.wm.cts"
+         android:label="CTS tests of WindowManager">
     </instrumentation>
 
 </manifest>
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index e17658a..0fdca3f 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -16,334 +16,279 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          package="android.server.wm.app">
+     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+     package="android.server.wm.app">
 
     <!-- virtual display test permissions -->
-    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
     <application>
         <activity android:name=".TestActivity"
-                android:resizeableActivity="true"
-                android:supportsPictureInPicture="true"
-                android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="true"
+             android:exported="true"/>
         <activity android:name=".TestActivityWithSameAffinity"
-                android:resizeableActivity="true"
-                android:supportsPictureInPicture="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.PipActivitySameAffinity"
-        />
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipActivitySameAffinity"/>
         <activity android:name=".TranslucentTestActivity"
-                android:resizeableActivity="true"
-                android:supportsPictureInPicture="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                android:theme="@style/Theme.Transparent"
-                android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:theme="@style/Theme.Transparent"
+             android:exported="true"/>
         <activity android:name=".VrTestActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"/>
         <activity-alias android:name=".AliasTestActivity"
-                  android:exported="true"
-                  android:targetActivity=".TestActivity"
-        />
+             android:exported="true"
+             android:targetActivity=".TestActivity"/>
         <activity android:name=".ResumeWhilePausingActivity"
-                android:allowEmbedded="true"
-                android:resumeWhilePausing="true"
-                android:taskAffinity=""
-                android:exported="true"
-        />
+             android:allowEmbedded="true"
+             android:resumeWhilePausing="true"
+             android:taskAffinity=""
+             android:exported="true"/>
         <activity android:name=".ResizeableActivity"
-                android:resizeableActivity="true"
-                android:allowEmbedded="true"
-                android:exported="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
-        />
+             android:resizeableActivity="true"
+             android:allowEmbedded="true"
+             android:exported="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/>
         <activity android:name=".NonResizeableActivity"
-                android:resizeableActivity="false"
-                android:exported="true"
-        />
+             android:resizeableActivity="false"
+             android:exported="true"/>
         <activity android:name=".DockedActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.DockedActivity"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.DockedActivity"/>
         <activity android:name=".TranslucentActivity"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar"
-            android:resizeableActivity="true"
-            android:taskAffinity="nobody.but.TranslucentActivity"
-            android:exported="true"
-        />
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"
+             android:resizeableActivity="true"
+             android:taskAffinity="nobody.but.TranslucentActivity"
+             android:exported="true"/>
         <activity android:name=".DialogWhenLargeActivity"
-                android:exported="true"
-                android:theme="@android:style/Theme.DeviceDefault.Light.DialogWhenLarge"
-        />
+             android:exported="true"
+             android:theme="@android:style/Theme.DeviceDefault.Light.DialogWhenLarge"/>
         <activity android:name=".NoRelaunchActivity"
-                android:resizeableActivity="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|fontScale|colorMode|density|touchscreen"
-                android:exported="true"
-                android:taskAffinity="nobody.but.NoRelaunchActivity"
-        />
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|fontScale|colorMode|density|touchscreen"
+             android:exported="true"
+             android:taskAffinity="nobody.but.NoRelaunchActivity"/>
         <activity android:name=".SlowCreateActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"/>
         <activity android:name=".LaunchingActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.LaunchingActivity"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.LaunchingActivity"/>
         <!--
-         * This activity should have same affinity as LaunchingActivity, because we're using it to
-         * check activities being launched into the same task.
-         -->
+                     * This activity should have same affinity as LaunchingActivity, because we're using it to
+                     * check activities being launched into the same task.
+                     -->
         <activity android:name=".AltLaunchingActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.LaunchingActivity"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.LaunchingActivity"/>
         <activity android:name=".PipActivity"
-                android:resizeableActivity="false"
-                android:supportsPictureInPicture="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                android:exported="true"
-                android:taskAffinity="nobody.but.PipActivity"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipActivity"/>
         <activity android:name=".PipActivity2"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.PipActivity2"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipActivity2"/>
         <activity android:name=".PipOnStopActivity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.PipOnStopActivity"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipOnStopActivity"/>
         <activity android:name=".PipActivityWithSameAffinity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.PipActivitySameAffinity"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipActivitySameAffinity"/>
         <activity android:name=".AlwaysFocusablePipActivity"
-                  android:theme="@style/Theme.Transparent"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  androidprv:alwaysFocusable="true"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.AlwaysFocusablePipActivity"
-        />
+             android:theme="@style/Theme.Transparent"
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             androidprv:alwaysFocusable="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.AlwaysFocusablePipActivity"/>
         <activity android:name=".LaunchIntoPinnedStackPipActivity"
-                  android:resizeableActivity="false"
-                  androidprv:alwaysFocusable="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-        />
+             android:resizeableActivity="false"
+             androidprv:alwaysFocusable="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"/>
         <activity android:name=".LaunchPipOnPipActivity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:taskAffinity="nobody.but.LaunchPipOnPipActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:taskAffinity="nobody.but.LaunchPipOnPipActivity"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"/>
         <activity android:name=".LaunchEnterPipActivity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  androidprv:alwaysFocusable="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             androidprv:alwaysFocusable="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"/>
         <activity android:name=".PipActivityWithMinimalSize"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.PipActivity">
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipActivity">
                   <layout android:minWidth="100dp"
-                          android:minHeight="80dp"
-                  />
+                       android:minHeight="80dp"/>
         </activity>
         <activity android:name=".FreeformActivity"
-                  android:resizeableActivity="true"
-                  android:taskAffinity="nobody.but.FreeformActivity"
-                  android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:taskAffinity="nobody.but.FreeformActivity"
+             android:exported="true"/>
         <activity android:name=".TopLeftLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
                   <layout android:defaultWidth="240dp"
-                          android:defaultHeight="160dp"
-                          android:gravity="top|left"
-                          android:minWidth="100dp"
-                          android:minHeight="80dp"
-                  />
+                       android:defaultHeight="160dp"
+                       android:gravity="top|left"
+                       android:minWidth="100dp"
+                       android:minHeight="80dp"/>
         </activity>
         <activity android:name=".TopRightLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
                   <layout android:defaultWidth="50%"
-                          android:defaultHeight="70%"
-                          android:gravity="top|right"
-                          android:minWidth="50dp"
-                          android:minHeight="80dp"
-                  />
+                       android:defaultHeight="70%"
+                       android:gravity="top|right"
+                       android:minWidth="50dp"
+                       android:minHeight="80dp"/>
         </activity>
         <activity android:name=".BottomLeftLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
                   <layout android:defaultWidth="50%"
-                          android:defaultHeight="70%"
-                          android:gravity="bottom|left"
-                          android:minWidth="50dp"
-                          android:minHeight="80dp"
-                  />
+                       android:defaultHeight="70%"
+                       android:gravity="bottom|left"
+                       android:minWidth="50dp"
+                       android:minHeight="80dp"/>
         </activity>
         <activity android:name=".BottomRightLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
                   <layout android:defaultWidth="240dp"
-                          android:defaultHeight="160dp"
-                          android:gravity="bottom|right"
-                          android:minWidth="100dp"
-                          android:minHeight="80dp"
-                  />
+                       android:defaultHeight="160dp"
+                       android:gravity="bottom|right"
+                       android:minWidth="100dp"
+                       android:minHeight="80dp"/>
         </activity>
         <activity android:name=".TurnScreenOnActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".TurnScreenOnDismissKeyguardActivity"
-            android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".SingleTaskActivity"
-            android:exported="true"
-            android:launchMode="singleTask"
-        />
+             android:exported="true"
+             android:launchMode="singleTask"/>
         <activity android:name=".SingleInstanceActivity"
-            android:exported="true"
-            android:launchMode="singleInstance"
-        />
+             android:exported="true"
+             android:launchMode="singleInstance"/>
         <activity android:name=".TrampolineActivity"
-                  android:exported="true"
-                  android:theme="@android:style/Theme.NoDisplay"
-        />
+             android:exported="true"
+             android:theme="@android:style/Theme.NoDisplay"/>
         <activity android:name=".BroadcastReceiverActivity"
-                  android:resizeableActivity="true"
-                  android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"/>
         <activity-alias android:enabled="true"
-                android:exported="true"
-                android:name=".EntryPointAliasActivity"
-                android:targetActivity=".TrampolineActivity" >
+             android:exported="true"
+             android:name=".EntryPointAliasActivity"
+             android:targetActivity=".TrampolineActivity">
         </activity-alias>
         <activity android:name=".BottomActivity"
-                  android:exported="true"
-                  android:theme="@style/NoPreview"
-        />
+             android:exported="true"
+             android:theme="@style/NoPreview"/>
         <activity android:name=".TopActivity"
-                  android:process=".top_process"
-                  android:exported="true"
-                  android:theme="@style/NoPreview"
-        />
+             android:process=".top_process"
+             android:exported="true"
+             android:theme="@style/NoPreview"/>
         <activity android:name=".UnresponsiveActivity"
-                  android:process=".unresponsive_activity_process"
-                  android:exported="true"
-                  android:theme="@style/NoPreview"
-        />
+             android:process=".unresponsive_activity_process"
+             android:exported="true"
+             android:theme="@style/NoPreview"/>
         <activity android:name=".TranslucentTopActivity"
-                  android:process=".top_process"
-                  android:exported="true"
-                  android:theme="@style/TranslucentTheme"
-        />
+             android:process=".top_process"
+             android:exported="true"
+             android:theme="@style/TranslucentTheme"/>
         <!-- An animation test with an explicitly opaque theme, overriding device defaults, as the
-             animation background being tested is not used in translucent activities. -->
+                         animation background being tested is not used in translucent activities. -->
         <activity android:name=".AnimationTestActivity"
-                  android:theme="@style/OpaqueTheme"
-                  android:exported="true"
-        />
+             android:theme="@style/OpaqueTheme"
+             android:exported="true"/>
         <activity android:name=".VirtualDisplayActivity"
-                  android:resizeableActivity="true"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.VirtualDisplayActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboardHidden"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.VirtualDisplayActivity"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboardHidden"/>
         <activity android:name=".ShowWhenLockedActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".ShowWhenLockedWithDialogActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".ShowWhenLockedDialogActivity"
-            android:exported="true"
-            android:theme="@android:style/Theme.Material.Dialog"
-        />
+             android:exported="true"
+             android:theme="@android:style/Theme.Material.Dialog"/>
         <activity android:name=".ShowWhenLockedTranslucentActivity"
-                  android:exported="true"
-                  android:theme="@android:style/Theme.Translucent"
-        />
+             android:exported="true"
+             android:theme="@android:style/Theme.Translucent"/>
         <activity android:name=".DismissKeyguardActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".DismissKeyguardMethodActivity"
-            android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".WallpaperActivity"
-            android:exported="true"
-            android:theme="@style/WallpaperTheme"
-        />
+             android:exported="true"
+             android:theme="@style/WallpaperTheme"/>
         <activity android:name=".InputMethodTestActivity"
-                android:exported="true" />
+             android:exported="true"/>
         <activity android:name=".KeyguardLockActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".LogConfigurationActivity"
-            android:exported="true"
-            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-        />
+             android:exported="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"/>
         <activity android:name=".PortraitOrientationActivity"
-                  android:exported="true"
-                  android:screenOrientation="portrait"
-                  android:documentLaunchMode="always"
-        />
+             android:exported="true"
+             android:screenOrientation="portrait"
+             android:documentLaunchMode="always"/>
         <activity android:name=".LandscapeOrientationActivity"
-                  android:exported="true"
-                  android:screenOrientation="landscape"
-                  android:documentLaunchMode="always"
-        />
+             android:exported="true"
+             android:screenOrientation="landscape"
+             android:documentLaunchMode="always"/>
         <activity android:name=".MoveTaskToBackActivity"
-                  android:exported="true"
-                  android:launchMode="singleInstance"
-        />
+             android:exported="true"
+             android:launchMode="singleInstance"/>
         <activity android:name=".NightModeActivity"
-                  android:exported="true"
-                  android:configChanges="uiMode"
-        />
+             android:exported="true"
+             android:configChanges="uiMode"/>
         <activity android:name=".FontScaleActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".FontScaleNoRelaunchActivity"
-                  android:exported="true"
-                  android:configChanges="fontScale"
-        />
+             android:exported="true"
+             android:configChanges="fontScale"/>
         <activity android:name=".DisplayAccessCheckEmbeddingActivity"
-                   android:allowEmbedded="true"
-                   android:exported="true"/>
-        <receiver
-            android:name=".LaunchBroadcastReceiver"
-            android:enabled="true"
-            android:exported="true" >
+             android:allowEmbedded="true"
+             android:exported="true"/>
+        <receiver android:name=".LaunchBroadcastReceiver"
+             android:enabled="true"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.server.wm.app.LAUNCH_BROADCAST_ACTION"/>
                 <action android:name="android.server.wm.app.ACTION_TEST_ACTIVITY_START"/>
@@ -351,168 +296,165 @@
         </receiver>
 
         <activity android:name=".AssistantActivity"
-            android:exported="true"
-            android:screenOrientation="locked" />
+             android:exported="true"
+             android:screenOrientation="locked"/>
         <activity android:name=".TranslucentAssistantActivity"
-            android:exported="true"
-            android:theme="@style/Theme.Transparent" />
+             android:exported="true"
+             android:theme="@style/Theme.Transparent"/>
         <activity android:name=".LaunchAssistantActivityFromSession"
-            android:taskAffinity="nobody.but.LaunchAssistantActivityFromSession"
-            android:exported="true" />
+             android:taskAffinity="nobody.but.LaunchAssistantActivityFromSession"
+             android:exported="true"/>
         <activity android:name=".LaunchAssistantActivityIntoAssistantStack"
-            android:taskAffinity="nobody.but.LaunchAssistantActivityIntoAssistantStack"
-            android:exported="true" />
+             android:taskAffinity="nobody.but.LaunchAssistantActivityIntoAssistantStack"
+             android:exported="true"/>
 
         <service android:name=".AssistantVoiceInteractionService"
-                 android:permission="android.permission.BIND_VOICE_INTERACTION"
-                 android:exported="true">
+             android:permission="android.permission.BIND_VOICE_INTERACTION"
+             android:exported="true">
             <meta-data android:name="android.voice_interaction"
-                       android:resource="@xml/interaction_service" />
+                 android:resource="@xml/interaction_service"/>
             <intent-filter>
-                <action android:name="android.service.voice.VoiceInteractionService" />
+                <action android:name="android.service.voice.VoiceInteractionService"/>
             </intent-filter>
         </service>
 
         <service android:name=".AssistantVoiceInteractionSessionService"
-                 android:permission="android.permission.BIND_VOICE_INTERACTION"
-                 android:exported="true" />
+             android:permission="android.permission.BIND_VOICE_INTERACTION"
+             android:exported="true"/>
 
         <activity android:name=".SplashscreenActivity"
-            android:taskAffinity="nobody.but.SplashscreenActivity"
-            android:theme="@style/SplashscreenTheme"
-            android:exported="true" />
+             android:taskAffinity="nobody.but.SplashscreenActivity"
+             android:theme="@style/SplashscreenTheme"
+             android:exported="true"/>
 
         <activity android:name=".NoHistoryActivity"
-                  android:noHistory="true"
-                  android:exported="true" />
+             android:noHistory="true"
+             android:exported="true"/>
 
         <activity android:name=".ShowWhenLockedAttrActivity"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".ShowWhenLockedAttrRemoveAttrActivity"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".ShowWhenLockedAttrWithDialogActivity"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".InheritShowWhenLockedAddActivity"
-            android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name=".InheritShowWhenLockedAttrActivity"
-                  android:inheritShowWhenLocked="true"
-                  android:exported="true" />
+             android:inheritShowWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".InheritShowWhenLockedRemoveActivity"
-                  android:inheritShowWhenLocked="true"
-                  android:exported="true" />
+             android:inheritShowWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".NoInheritShowWhenLockedAttrActivity"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name=".ShowWhenLockedAttrImeActivity"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".ShowWhenLockedAttrRotationActivity"
-                  android:showWhenLocked="true"
-                  android:configChanges="orientation|screenSize"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:configChanges="orientation|screenSize"
+             android:exported="true"/>
 
         <activity android:name=".ToastActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
 
         <activity android:name=".TurnScreenOnAttrActivity"
-                  android:turnScreenOn="true"
-                  android:exported="true" />
+             android:turnScreenOn="true"
+             android:exported="true"/>
 
         <activity android:name=".TurnScreenOnShowOnLockActivity"
-                  android:showWhenLocked="true"
-                  android:turnScreenOn="true"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:turnScreenOn="true"
+             android:exported="true"/>
 
         <activity android:name=".TurnScreenOnAttrRemoveAttrActivity"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".TurnScreenOnSingleTaskActivity"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:exported="true"
-                  android:launchMode="singleTask" />
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"
+             android:exported="true"
+             android:launchMode="singleTask"/>
 
         <activity android:name=".TurnScreenOnAttrDismissKeyguardActivity"
-                  android:turnScreenOn="true"
-                  android:exported="true"/>
+             android:turnScreenOn="true"
+             android:exported="true"/>
 
         <activity android:name=".TurnScreenOnWithRelayoutActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
 
         <activity android:name=".RecursiveActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
 
         <activity android:name=".LaunchTestOnDestroyActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
 
         <activity android:name=".ReportFullyDrawnActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
 
         <activity android:name=".NoDisplayActivity"
-                  android:exported="true"
-                  android:theme="@android:style/Theme.NoDisplay"/>
+             android:exported="true"
+             android:theme="@android:style/Theme.NoDisplay"/>
 
         <activity android:name=".SingleTaskInstanceDisplayActivity"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name=".SingleTaskInstanceDisplayActivity2"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name=".SingleTaskInstanceDisplayActivity3"
-                  android:exported="true"
-                  android:launchMode="singleInstance" />
+             android:exported="true"
+             android:launchMode="singleInstance"/>
 
-        <service
-            android:name=".LiveWallpaper"
-            android:permission="android.permission.BIND_WALLPAPER">
+        <service android:name=".LiveWallpaper"
+             android:permission="android.permission.BIND_WALLPAPER"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.service.wallpaper.WallpaperService">
                 </action>
             </intent-filter>
-            <meta-data
-                android:name="android.service.wallpaper"
-                android:resource="@xml/wallpaper">
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/wallpaper">
             </meta-data>
         </service>
 
-        <service
-            android:name=".TestDream"
-            android:exported="true"
-            android:permission="android.permission.BIND_DREAM_SERVICE">
+        <service android:name=".TestDream"
+             android:exported="true"
+             android:permission="android.permission.BIND_DREAM_SERVICE">
             <intent-filter>
-                <action android:name="android.service.dreams.DreamService" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.service.dreams.DreamService"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <service
-            android:name=".TestStubbornDream"
-            android:exported="true"
-            android:permission="android.permission.BIND_DREAM_SERVICE">
+        <service android:name=".TestStubbornDream"
+             android:exported="true"
+             android:permission="android.permission.BIND_DREAM_SERVICE">
             <intent-filter>
-                <action android:name="android.service.dreams.DreamService" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.service.dreams.DreamService"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
         <!-- Disable home activities by default or it may disturb other tests by
-             showing ResolverActivity when start home activity -->
+                         showing ResolverActivity when start home activity -->
         <activity-alias android:name=".HomeActivity"
-                        android:targetActivity=".TestActivity"
-                        android:enabled="false"
-                        android:exported="true">
+             android:targetActivity=".TestActivity"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -521,9 +463,9 @@
         </activity-alias>
 
         <activity-alias android:name=".SecondaryHomeActivity"
-                        android:targetActivity=".TestActivity"
-                        android:enabled="false"
-                        android:exported="true">
+             android:targetActivity=".TestActivity"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -533,9 +475,9 @@
         </activity-alias>
 
         <activity-alias android:name=".SingleHomeActivity"
-                        android:targetActivity=".SingleInstanceActivity"
-                        android:enabled="false"
-                        android:exported="true">
+             android:targetActivity=".SingleInstanceActivity"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -544,9 +486,9 @@
         </activity-alias>
 
         <activity-alias android:name=".SingleSecondaryHomeActivity"
-                        android:targetActivity=".SingleInstanceActivity"
-                        android:enabled="false"
-                        android:exported="true">
+             android:targetActivity=".SingleInstanceActivity"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -556,43 +498,41 @@
         </activity-alias>
 
         <service android:name="com.android.cts.verifier.vr.MockVrListenerService"
-                 android:exported="true"
-                 android:enabled="true"
-                 android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
+             android:exported="true"
+             android:enabled="true"
+             android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
            <intent-filter>
-               <action android:name="android.service.vr.VrListenerService" />
+               <action android:name="android.service.vr.VrListenerService"/>
            </intent-filter>
         </service>
 
         <activity android:name=".HostActivity"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.server.wm.app.HostActivity"></action>
+                <action android:name="android.server.wm.app.HostActivity"/>
             </intent-filter>
         </activity>
         <service android:name=".RenderService"
-                 android:process=".render_process" />
-        <activity
-            android:name=".ClickableToastActivity"
+             android:process=".render_process"/>
+        <activity android:name=".ClickableToastActivity"
+             android:exported="true"/>
+        <activity android:name=".MinimalPostProcessingActivity"
+             android:exported="true"/>
+        <activity android:name=".MinimalPostProcessingActivity2"
+             android:exported="true"/>
+        <activity android:name=".MinimalPostProcessingManifestActivity"
+             android:preferMinimalPostProcessing="true"
+             android:exported="true"/>
+        <activity android:name=".PopupMinimalPostProcessingActivity"
+             android:theme="@android:style/Theme.Holo.Dialog.NoActionBar"
+             android:exported="true"/>
+        <activity android:name=".CrashingActivity"
             android:exported="true" />
-        <activity
-            android:name=".MinimalPostProcessingActivity"
-            android:exported="true" />
-        <activity
-            android:name=".MinimalPostProcessingActivity2"
-            android:exported="true"/>
-        <activity
-            android:name=".MinimalPostProcessingManifestActivity"
-            android:preferMinimalPostProcessing="true"
-            android:exported="true"/>
-        <activity
-            android:name=".PopupMinimalPostProcessingActivity"
-            android:theme="@android:style/Theme.Holo.Dialog.NoActionBar"
-            android:exported="true" />
-        <activity
-            android:name=".PresentationActivity"
-            android:launchMode="singleTop"
-            android:exported="true" />
+        <activity android:name=".PresentationActivity"
+             android:launchMode="singleTop"
+             android:exported="true"/>
+
+        <service android:name=".OverlayTestService"
+                 android:exported="true" />
     </application>
 </manifest>
-
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index 108e034..fa61c92 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -207,12 +207,18 @@
     public static final ComponentName POPUP_MPP_ACTIVITY =
             component("PopupMinimalPostProcessingActivity");
 
+    public static final ComponentName CRASHING_ACTIVITY =
+            component("CrashingActivity");
+
     public static final ComponentName TEST_DREAM_SERVICE =
             component("TestDream");
 
     public static final ComponentName TEST_STUBBORN_DREAM_SERVICE =
             component("TestStubbornDream");
 
+    public static final ComponentName OVERLAY_TEST_SERVICE =
+            component("OverlayTestService");
+
     /**
      * Action and extra key constants for {@link #INPUT_METHOD_TEST_ACTIVITY}.
      */
@@ -311,6 +317,11 @@
         public static final String EXTRA_FONT_ACTIVITY_DPI = "fontActivityDpi";
     }
 
+    /** Extra key constants for {@link android.server.wm.app.NoHistoryActivity}. */
+    public static class NoHistoryActivity {
+        public static final String EXTRA_SHOW_WHEN_LOCKED = "showWhenLocked";
+    }
+
     /** Extra key constants for {@link android.server.wm.app.TurnScreenOnActivity}. */
     public static class TurnScreenOnActivity {
         // Turn on screen by window flags or APIs.
@@ -407,6 +418,8 @@
         // Calls requestAutoEnterPictureInPicture() with the value provided
         public static final String EXTRA_ENTER_PIP_ON_PIP_REQUESTED =
                 "enter_pip_on_pip_requested";
+        // Sets auto PIP allowed on the activity picture-in-picture params.
+        public static final String EXTRA_ALLOW_AUTO_PIP = "enter_pip_auto_pip_allowed";
         // Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
         public static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
         // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
@@ -500,6 +513,15 @@
         public static final String KEY_FINISH_BEFORE_LAUNCH = "finish_before_launch";
     }
 
+    public static class OverlayTestService {
+        public static final String EXTRA_WINDOW_NAME = "window_name";
+    }
+
+    public static class Notifications {
+        public static final String CHANNEL_MAIN = "main";
+        public static final int ID_OVERLAY_TEST_SERVICE = 1;
+    }
+
     private static ComponentName component(String className) {
         return component(Components.class, className);
     }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/CrashingActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/CrashingActivity.java
new file mode 100644
index 0000000..7ca8090
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/CrashingActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+/** This activity will instantly crash with a RuntimeException upon receiving any intent. */
+public class CrashingActivity extends Activity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        throw new RuntimeException("Crashing for testing purposes!");
+    }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/NoHistoryActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/NoHistoryActivity.java
index 83f0587..edf727a 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/NoHistoryActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/NoHistoryActivity.java
@@ -16,8 +16,21 @@
 
 package android.server.wm.app;
 
+import android.os.Bundle;
+
 /**
  * An activity that has the noHistory flag set.
  */
 public class NoHistoryActivity extends AbstractLifecycleLogActivity {
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        if (getIntent().getBooleanExtra(Components.NoHistoryActivity.EXTRA_SHOW_WHEN_LOCKED,
+                false)) {
+            setShowWhenLocked(true);
+            setTurnScreenOn(true);
+        }
+    }
 }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/OverlayTestService.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/OverlayTestService.java
new file mode 100644
index 0000000..d70aaa5
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/OverlayTestService.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.app;
+
+import static android.server.wm.app.Components.OverlayTestService.EXTRA_WINDOW_NAME;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.IBinder;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+
+public class OverlayTestService extends Service {
+    private WindowManager mWindowManager;
+    private View mView;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWindowManager = getSystemService(WindowManager.class);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent != null && intent.hasExtra(EXTRA_WINDOW_NAME)) {
+            // Have to be a foreground service since this app is in the background
+            startForeground();
+            addWindow(intent.getStringExtra(EXTRA_WINDOW_NAME));
+        }
+        return START_NOT_STICKY;
+    }
+
+    private void addWindow(final String windowName) {
+        mView = new View(this);
+        mView.setBackgroundColor(Color.RED);
+        WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+        p.setTitle(windowName);
+        p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN | FLAG_NOT_TOUCHABLE;
+        p.width = 100;
+        p.height = 100;
+        p.alpha = 0.5f;
+        p.gravity = Gravity.CENTER;
+        p.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mWindowManager.addView(mView, p);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mView != null) {
+            mWindowManager.removeViewImmediate(mView);
+            mView = null;
+        }
+    }
+
+    private void startForeground() {
+        String channel = Components.Notifications.CHANNEL_MAIN;
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        notificationManager.createNotificationChannel(
+                new NotificationChannel(channel, channel, NotificationManager.IMPORTANCE_DEFAULT));
+        Notification notification =
+                new Notification.Builder(this, channel)
+                        .setContentTitle("CTS")
+                        .setContentText(getClass().getCanonicalName())
+                        .setSmallIcon(android.R.drawable.btn_default)
+                        .build();
+        startForeground(Components.Notifications.ID_OVERLAY_TEST_SERVICE, notification);
+    }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
index 26f7b1e..aeb69f6 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
@@ -25,6 +25,7 @@
 import static android.server.wm.app.Components.PipActivity.ACTION_MOVE_TO_BACK;
 import static android.server.wm.app.Components.PipActivity.ACTION_ON_PIP_REQUESTED;
 import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ALLOW_AUTO_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_DISMISS_KEYGUARD;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
@@ -169,6 +170,12 @@
             }
         }
 
+        if (getIntent().hasExtra(EXTRA_ALLOW_AUTO_PIP)) {
+            final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
+            builder.setAutoEnterEnabled(true);
+            setPictureInPictureParams(builder.build());
+        }
+
         // Enable tap to finish if necessary
         if (getIntent().hasExtra(EXTRA_TAP_TO_FINISH)) {
             setContentView(R.layout.tap_to_finish_pip_layout);
diff --git a/tests/framework/base/windowmanager/appSecondUid/Android.bp b/tests/framework/base/windowmanager/appSecondUid/Android.bp
index 9d2aab6..6a725e0 100644
--- a/tests/framework/base/windowmanager/appSecondUid/Android.bp
+++ b/tests/framework/base/windowmanager/appSecondUid/Android.bp
@@ -16,7 +16,10 @@
     name: "CtsDeviceServicesTestSecondApp",
     defaults: ["cts_support_defaults"],
 
-    static_libs: ["cts-wm-app-base"],
+    static_libs: [
+        "cts-wm-app-base",
+        "cts-wm-overlayapp-base"
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/tests/framework/base/windowmanager/appThirdUid/Android.bp b/tests/framework/base/windowmanager/appThirdUid/Android.bp
index c333a56..ebd8ccc 100644
--- a/tests/framework/base/windowmanager/appThirdUid/Android.bp
+++ b/tests/framework/base/windowmanager/appThirdUid/Android.bp
@@ -16,7 +16,10 @@
     name: "CtsDeviceServicesTestThirdApp",
     defaults: ["cts_support_defaults"],
 
-    static_libs: ["cts-wm-app-base"],
+    static_libs: [
+        "cts-wm-app-base",
+        "cts-wm-overlayapp-base"
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml b/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
index eb156b6..5b47fc0 100755
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
@@ -16,37 +16,32 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.server.wm.backgroundactivity.appa">
+     package="android.server.wm.backgroundactivity.appa">
 
     <!-- To enable the app to start activities from the background. -->
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
     <application android:testOnly="true">
-        <receiver
-            android:name=".StartBackgroundActivityReceiver"
-            android:exported="true"/>
-        <receiver
-            android:name=".SendPendingIntentReceiver"
-            android:exported="true"/>
-        <receiver
-            android:name=".SimpleAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name=".StartBackgroundActivityReceiver"
+             android:exported="true"/>
+        <receiver android:name=".SendPendingIntentReceiver"
+             android:exported="true"/>
+        <receiver android:name=".SimpleAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name=".ForegroundActivity"
-            android:taskAffinity=".am_cts_bg_task_a"
-            android:exported="true" />
-        <activity
-            android:name=".BackgroundActivity"
-            android:taskAffinity=".am_cts_bg_task_b"
-            android:exported="true" />
-        <activity
-            android:name=".SecondBackgroundActivity"
-            android:exported="true" />
+        <activity android:name=".ForegroundActivity"
+             android:taskAffinity=".am_cts_bg_task_a"
+             android:exported="true"/>
+        <activity android:name=".BackgroundActivity"
+             android:taskAffinity=".am_cts_bg_task_b"
+             android:exported="true"/>
+        <activity android:name=".SecondBackgroundActivity"
+             android:exported="true"/>
     </application>
 </manifest>
diff --git a/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml b/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml
index c044981..bb2a1b3 100644
--- a/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml
@@ -15,11 +15,12 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.server.wm.dndsourceapp"
-        android:targetSandboxVersion="2">
+     package="android.server.wm.dndsourceapp"
+     android:targetSandboxVersion="2">
     <application android:label="CtsDnDSource">
         <activity android:name="android.server.wm.dndsourceapp.DragSource"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout">
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -27,7 +28,7 @@
         </activity>
 
         <provider android:name="android.server.wm.dndsourceapp.DragSourceContentProvider"
-                  android:authorities="android.server.wm.dndsource.contentprovider"
-                  android:grantUriPermissions="true"/>
+             android:authorities="android.server.wm.dndsource.contentprovider"
+             android:grantUriPermissions="true"/>
     </application>
 </manifest>
diff --git a/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml b/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml
index 7d50b70..6cdddae 100644
--- a/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml
@@ -15,11 +15,12 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.server.wm.dndtargetapp"
-        android:targetSandboxVersion="2">
+     package="android.server.wm.dndtargetapp"
+     android:targetSandboxVersion="2">
     <application android:label="CtsDnDTarget">
         <activity android:name="android.server.wm.dndtargetapp.DropTarget"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout">
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml b/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml
index d10a548..106415c 100644
--- a/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml
@@ -15,9 +15,10 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.server.wm.dndtargetappsdk23">
+     package="android.server.wm.dndtargetappsdk23">
     <application android:label="CtsDnDTarget">
-        <activity android:name="android.server.wm.dndtargetappsdk23.DropTarget">
+        <activity android:name="android.server.wm.dndtargetappsdk23.DropTarget"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/tests/framework/base/windowmanager/overlayappbase/Android.bp b/tests/framework/base/windowmanager/overlayappbase/Android.bp
new file mode 100644
index 0000000..b403549
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+android_test_helper_app {
+    name: "cts-wm-overlayapp-base",
+    defaults: ["cts_support_defaults"],
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    static_libs: [
+        "cts-wm-app-base",
+        "androidx.annotation_annotation",
+    ],
+
+    sdk_version: "test_current",
+}
diff --git a/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml b/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml
new file mode 100644
index 0000000..763eb15
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.server.wm.overlay">
+
+    <!-- We use SAWs to create obscuring windows for test WindowUntrustedTouchTest -->
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application>
+        <receiver
+            android:name="android.server.wm.overlay.ActionReceiver"
+            android:exported="true" />
+        <activity
+            android:name="android.server.wm.overlay.OverlayActivity"
+            android:theme="@android:style/Theme.Translucent"
+            android:exported="true" />
+        <activity
+            android:name="android.server.wm.overlay.ToastActivity"
+            android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ActionReceiver.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ActionReceiver.java
new file mode 100644
index 0000000..9ff5691
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ActionReceiver.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.overlay;
+
+import static android.server.wm.overlay.Components.ActionReceiver.ACTION_OVERLAY;
+import static android.server.wm.overlay.Components.ActionReceiver.ACTION_PING;
+import static android.server.wm.overlay.Components.ActionReceiver.CALLBACK_PONG;
+import static android.server.wm.overlay.Components.ActionReceiver.EXTRA_CALLBACK;
+import static android.server.wm.overlay.Components.ActionReceiver.EXTRA_NAME;
+import static android.server.wm.overlay.Components.ActionReceiver.EXTRA_OPACITY;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+
+public class ActionReceiver extends BroadcastReceiver {
+    private static final String TAG = "ActionReceiver";
+    public static final int BACKGROUND_COLOR = 0xFF00FF00;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        switch (action) {
+            case ACTION_OVERLAY:
+                String name = intent.getStringExtra(EXTRA_NAME);
+                float opacity = intent.getFloatExtra(EXTRA_OPACITY, 1f);
+                overlay(context, name, opacity);
+                break;
+            case ACTION_PING:
+                IBinder callback = intent.getExtras().getBinder(EXTRA_CALLBACK);
+                try {
+                    callback.transact(CALLBACK_PONG, Parcel.obtain(), null, IBinder.FLAG_ONEWAY);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Caller (test) died while sending response to ping", e);
+                }
+                break;
+            default:
+                Log.e(TAG, "Unknown action " + action);
+                break;
+        }
+    }
+
+    private void overlay(Context context, String name, float opacity) {
+        Context windowContext = getContextForOverlay(context);
+        View view = new View(windowContext);
+        view.setBackgroundColor(BACKGROUND_COLOR);
+        LayoutParams params =
+                new LayoutParams(
+                        LayoutParams.MATCH_PARENT,
+                        LayoutParams.MATCH_PARENT,
+                        LayoutParams.TYPE_APPLICATION_OVERLAY,
+                        LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE,
+                        PixelFormat.TRANSLUCENT);
+        params.setTitle(name);
+        params.alpha = opacity;
+        WindowManager windowManager = windowContext.getSystemService(WindowManager.class);
+        windowManager.addView(view, params);
+    }
+
+    private Context getContextForOverlay(Context context) {
+        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        Context displayContext = context.createDisplayContext(display);
+        return displayContext.createWindowContext(LayoutParams.TYPE_APPLICATION_OVERLAY, null);
+    }
+}
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java
new file mode 100644
index 0000000..ee7942c
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.overlay;
+
+import android.content.ComponentName;
+import android.os.Binder;
+import android.server.wm.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+    public interface ActionReceiver {
+        ComponentName COMPONENT = component("ActionReceiver");
+        String ACTION_OVERLAY = "overlay";
+        String ACTION_PING = "ping";
+        String EXTRA_NAME = "name";
+        String EXTRA_OPACITY = "opacity";
+        String EXTRA_CALLBACK = "callback";
+        int CALLBACK_PONG = Binder.FIRST_CALL_TRANSACTION;
+    }
+
+    public interface OverlayActivity {
+        ComponentName COMPONENT = component("OverlayActivity");
+        String EXTRA_NAME = "name";
+        String EXTRA_OPACITY = "opacity";
+    }
+
+    public interface ToastActivity {
+        ComponentName COMPONENT = component("ToastActivity");
+        String EXTRA_CUSTOM = "custom";
+    }
+
+    private static ComponentName component(String className) {
+        return component(Components.class, className);
+    }
+}
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java
new file mode 100644
index 0000000..8cde0f0
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.overlay;
+
+import static android.server.wm.overlay.ActionReceiver.BACKGROUND_COLOR;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+
+import androidx.annotation.Nullable;
+
+public class OverlayActivity extends Activity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        View view = new View(this);
+        view.setBackgroundColor(BACKGROUND_COLOR);
+        setContentView(view);
+        Window window = getWindow();
+        window.getAttributes().alpha = getIntent().getFloatExtra(
+                Components.OverlayActivity.EXTRA_OPACITY, 1f);
+        window.addFlags(LayoutParams.FLAG_NOT_TOUCHABLE);
+        window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE);
+    }
+}
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java
new file mode 100644
index 0000000..b9641c0
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.overlay;
+
+import static android.server.wm.overlay.ActionReceiver.BACKGROUND_COLOR;
+
+import android.app.Activity;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Toast;
+
+public class ToastActivity extends Activity {
+    @Override
+    protected void onResume() {
+        super.onResume();
+        // For toast matters, foreground means having an activity resumed on screen, so doing this
+        // on onResume()
+        boolean custom = getIntent().getBooleanExtra(Components.ToastActivity.EXTRA_CUSTOM, false);
+        final Toast toast;
+        if (custom) {
+            toast = new Toast(this);
+            View view = new View(this);
+            view.setBackgroundColor(BACKGROUND_COLOR);
+            toast.setView(view);
+            toast.setGravity(Gravity.FILL, 0, 0);
+            toast.setDuration(Toast.LENGTH_LONG);
+        } else {
+            toast = Toast.makeText(this, "Toast window", Toast.LENGTH_LONG);
+        }
+        toast.show();
+        finish();
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
index 3aa6fec..93382ad 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
@@ -17,13 +17,17 @@
 package android.server.wm;
 
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.server.wm.ActivityLauncher.KEY_NEW_TASK;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
+import android.app.Activity;
 import android.app.ActivityOptions;
+import android.app.Instrumentation;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Handler;
@@ -32,6 +36,8 @@
 import android.server.wm.cts.R;
 import android.util.Range;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import org.junit.Test;
 
 import java.util.concurrent.CountDownLatch;
@@ -43,9 +49,66 @@
  */
 @Presubmit
 public class ActivityTransitionTests extends ActivityManagerTestBase {
+    // See WindowManagerService.DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
+    static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
+            "persist.wm.disable_custom_task_animation";
+    static final boolean DISABLE_CUSTOM_TASK_ANIMATION_DEFAULT = true;
+
+    private static boolean customTaskAnimationDisabled() {
+        try {
+            return Integer.parseInt(executeShellCommand(
+                    "getprop " + DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY).replace("\n", "")) != 0;
+        } catch (NumberFormatException e) {
+            return DISABLE_CUSTOM_TASK_ANIMATION_DEFAULT;
+        }
+    }
+
     @Test
     public void testActivityTransitionDurationNoShortenAsExpected() throws Exception {
-        final long expectedDurationMs = 500L - 100L;
+        final long expectedDurationMs = 500L - 100L;    // custom animation
+        final long minDurationMs = expectedDurationMs;
+        final long maxDurationMs = expectedDurationMs + 300L;
+        final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        long[] transitionStartTime = new long[1];
+        long[] transitionEndTime = new long[1];
+
+        final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
+            transitionStartTime[0] = System.currentTimeMillis();
+        };
+
+        final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
+            transitionEndTime[0] = System.currentTimeMillis();
+            latch.countDown();
+        };
+
+        final Intent intent = new Intent(mContext, LauncherActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final LauncherActivity launcherActivity =
+                (LauncherActivity) instrumentation.startActivitySync(intent);
+
+        final Bundle bundle = ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.alpha, 0, new Handler(Looper.getMainLooper()), startedListener,
+                finishedListener).toBundle();
+        launcherActivity.startTransitionActivity(bundle);
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+        waitAndAssertTopResumedActivity(new ComponentName(mContext, TransitionActivity.class),
+                DEFAULT_DISPLAY, "Activity must be launched");
+
+        latch.await(2, TimeUnit.SECONDS);
+        final long totalTime = transitionEndTime[0] - transitionStartTime[0];
+        assertTrue("Actual transition duration should be in the range "
+                + "<" + minDurationMs + ", " + maxDurationMs + "> ms, "
+                + "actual=" + totalTime, durationRange.contains(totalTime));
+    }
+
+    @Test
+    public void testTaskTransitionDurationNoShortenAsExpected() throws Exception {
+        assumeFalse(customTaskAnimationDisabled());
+
+        final long expectedDurationMs = 500L - 100L;    // custom animation
         final long minDurationMs = expectedDurationMs;
         final long maxDurationMs = expectedDurationMs + 300L;
         final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
@@ -79,4 +142,55 @@
                 + "<" + minDurationMs + ", " + maxDurationMs + "> ms, "
                 + "actual=" + totalTime, durationRange.contains(totalTime));
     }
+
+    @Test
+    public void testTaskTransitionOverrideDisabled() throws Exception {
+        assumeTrue(customTaskAnimationDisabled());
+
+        final long expectedDurationMs = 275L - 100L;   // wallpaper close animation
+        final long minDurationMs = expectedDurationMs;
+        final long maxDurationMs = expectedDurationMs + 300L;
+        final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        long[] transitionStartTime = new long[1];
+        long[] transitionEndTime = new long[1];
+
+        final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
+            transitionStartTime[0] = System.currentTimeMillis();
+        };
+
+        final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
+            transitionEndTime[0] = System.currentTimeMillis();
+            latch.countDown();
+        };
+
+        // Overriding task transit animation is disabled, so default wallpaper close animation
+        // is played.
+        final Bundle bundle = ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.alpha, 0, new Handler(Looper.getMainLooper()), startedListener,
+                finishedListener).toBundle();
+        final Intent intent = new Intent().setComponent(TEST_ACTIVITY)
+                .addFlags(FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent, bundle);
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+        waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                "Activity must be launched");
+
+        latch.await(2, TimeUnit.SECONDS);
+        final long totalTime = transitionEndTime[0] - transitionStartTime[0];
+        assertTrue("Actual transition duration should be in the range "
+                + "<" + minDurationMs + ", " + maxDurationMs + "> ms, "
+                + "actual=" + totalTime, durationRange.contains(totalTime));
+    }
+
+    public static class LauncherActivity extends Activity {
+
+        public void startTransitionActivity(Bundle bundle) {
+            startActivity(new Intent(this, TransitionActivity.class), bundle);
+        }
+    }
+
+    public static class TransitionActivity extends Activity {
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index b052e88..6e4d1c1 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -101,7 +101,7 @@
                 LAUNCH_PIP_ON_PIP_ACTIVITY);
 
         assertNotEquals(stackId, INVALID_STACK_ID);
-        moveTopActivityToPinnedStack(stackId);
+        moveTopActivityToPinnedRootTask(stackId);
         mWmState.waitForValidState(
                 new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
                         .setWindowingMode(WINDOWING_MODE_PINNED)
@@ -151,7 +151,7 @@
                 ALWAYS_FOCUSABLE_PIP_ACTIVITY);
 
         assertNotEquals(stackId, INVALID_STACK_ID);
-        moveTopActivityToPinnedStack(stackId);
+        moveTopActivityToPinnedRootTask(stackId);
         mWmState.waitForValidState(
                 new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
                         .setWindowingMode(WINDOWING_MODE_PINNED)
@@ -169,7 +169,7 @@
             return;
         }
 
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
         forceStopHome();
 
         assertEquals(mWmState.getResumedActivitiesCount(), 0);
@@ -460,6 +460,29 @@
     }
 
     /**
+     * Asserts that a no-history activity is not stopped and removed after a translucent activity
+     * above becomes resumed.
+     */
+    @Test
+    public void testNoHistoryActivityNotFinishedBehindTranslucentActivity() {
+        // Launch a no-history activity
+        launchActivity(NO_HISTORY_ACTIVITY);
+
+        // Launch a translucent activity
+        launchActivity(TRANSLUCENT_ACTIVITY);
+
+        // Wait for the activity resumed
+        mWmState.waitForActivityState(TRANSLUCENT_ACTIVITY, STATE_RESUMED);
+        mWmState.assertVisibility(NO_HISTORY_ACTIVITY, true);
+
+        pressBackButton();
+
+        // Wait for the activity resumed
+        mWmState.waitForActivityState(NO_HISTORY_ACTIVITY, STATE_RESUMED);
+        mWmState.assertVisibility(NO_HISTORY_ACTIVITY, true);
+    }
+
+    /**
      *  If the next activity hasn't reported idle but it has drawn and the transition has done, the
      *  previous activity should be stopped and invisible without waiting for idle timeout.
      */
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
index 52d8351..0747859 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
@@ -58,6 +58,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
@@ -76,6 +77,7 @@
 import org.junit.Test;
 
 import java.util.List;
+import java.util.function.Function;
 
 /**
  * Build/Install/Run:
@@ -263,7 +265,7 @@
         separateTestJournal();
         final int width = displayRect.width();
         final int height = displayRect.height();
-        resizeDockedStack(width /* stackWidth */, height /* stackHeight */,
+        resizePrimarySplitScreen(width /* stackWidth */, height /* stackHeight */,
                 width /* taskWidth */, height /* taskHeight */);
 
         // Move activity back to fullscreen stack.
@@ -308,7 +310,7 @@
         final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density);
         final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density);
 
-        resizeDockedStack(0, 0, smallWidthPx, smallHeightPx);
+        resizePrimarySplitScreen(0, 0, smallWidthPx, smallHeightPx);
         mWmState.waitForValidState(
                 new WaitForValidActivityState.Builder(DIALOG_WHEN_LARGE_ACTIVITY)
                         .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
@@ -651,14 +653,41 @@
     }
 
     /**
-     * Test that the orientation for a simulated display context will not change when the device is
-     * rotated.
+     * Test that the orientation for a simulated display context derived from an application context
+     * will not change when the device rotates.
      */
     @Test
     public void testAppContextDerivedDisplayContextOrientationWhenRotating() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
         assumeTrue("Skipping test: no multi-display support", supportsMultiDisplay());
 
+        assertDisplayContextDoesntChangeOrientationWhenRotating(Activity::getApplicationContext);
+    }
+
+    /**
+     * Test that the orientation for a simulated display context derived from an activity context
+     * will not change when the device rotates.
+     */
+    @Test
+    public void testActivityContextDerivedDisplayContextOrientationWhenRotating() {
+        assumeTrue("Skipping test: no rotation support", supportsRotation());
+        assumeTrue("Skipping test: no multi-display support", supportsMultiDisplay());
+
+        assertDisplayContextDoesntChangeOrientationWhenRotating(activity -> activity);
+    }
+
+    /**
+     * Asserts that the orientation for a simulated display context derived from a base context will
+     * not change when the device rotates.
+     *
+     * @param baseContextSupplier function that returns a base context used to created the display
+     *                            context.
+     *
+     * @see #testAppContextDerivedDisplayContextOrientationWhenRotating
+     * @see #testActivityContextDerivedDisplayContextOrientationWhenRotating
+     */
+    private void assertDisplayContextDoesntChangeOrientationWhenRotating(
+            Function<Activity, Context> baseContextSupplier) {
         RotationSession rotationSession = createManagedRotationSession();
         rotationSession.set(ROTATION_0);
 
@@ -676,7 +705,7 @@
 
         DisplayManager dm = activity.getSystemService(DisplayManager.class);
         Display simulatedDisplay = dm.getDisplay(displayContent.mId);
-        Context simulatedDisplayContext = activity.getApplicationContext()
+        Context simulatedDisplayContext = baseContextSupplier.apply(activity)
                 .createDisplayContext(simulatedDisplay);
         assertEquals(ORIENTATION_PORTRAIT,
                 simulatedDisplayContext.getResources().getConfiguration().orientation);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
index 267bec9..2e3e414 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
@@ -204,7 +204,7 @@
         // started in setUp() will not allow any other activities to start. Therefore we should
         // remove it before launching a fullscreen activity.
         if (isAssistantOnTop()) {
-            removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+            removeRootTasksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
         }
 
         // Launch an assistant activity on top of an existing fullscreen activity, and ensure that
@@ -248,7 +248,7 @@
             assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
 
             // Go home, launch the assistant and check to see that home is visible
-            removeStacksInWindowingModes(WINDOWING_MODE_FULLSCREEN,
+            removeRootTasksInWindowingModes(WINDOWING_MODE_FULLSCREEN,
                     WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
             pressHomeButton();
             resumeAppSwitches();
@@ -264,7 +264,7 @@
 
             // Launch a fullscreen app and then launch the assistant and check to see that it is
             // also visible
-            removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+            removeRootTasksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
             launchActivityOnDisplay(TEST_ACTIVITY, mAssistantDisplayId);
             launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
                     EXTRA_ASSISTANT_IS_TRANSLUCENT, "true");
@@ -275,7 +275,7 @@
 
             // Go home, launch assistant, launch app into fullscreen with activity present, and go
             // back.Ensure home is visible.
-            removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+            removeRootTasksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
             pressHomeButton();
             resumeAppSwitches();
             launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
@@ -298,7 +298,7 @@
             // that it
             // is also visible
             if (supportsSplitScreenMultiWindow() &&  assistantRunsOnPrimaryDisplay()) {
-                removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+                removeRootTasksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
                 launchActivitiesInSplitScreen(
                         getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
                         getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
index fa5884a..1b1b83a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
@@ -16,6 +16,7 @@
 
 package android.server.wm;
 
+import static android.server.wm.WindowManagerState.STATE_STOPPED;
 import static android.server.wm.app.Components.TEST_DREAM_SERVICE;
 import static android.server.wm.app.Components.TEST_STUBBORN_DREAM_SERVICE;
 import static android.server.wm.ComponentNameUtils.getWindowName;
@@ -30,6 +31,7 @@
 import android.content.ComponentName;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.server.wm.app.Components;
 import android.view.Surface;
 
 import androidx.test.filters.FlakyTest;
@@ -186,4 +188,81 @@
         waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
                 "Dream activity should be the top resumed activity");
     }
+
+    @Test
+    public void testStartActivityDoesNotWakeAndIsNotResumed() {
+        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
+            launchActivity(Components.TEST_ACTIVITY);
+            mWmState.waitForActivityState(Components.TEST_ACTIVITY, STATE_STOPPED);
+            assertTrue(getIsDreaming());
+        }
+    }
+
+    @Test
+    public void testStartTurnScreenOnActivityDoesWake() {
+        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
+            launchActivity(Components.TURN_SCREEN_ON_ACTIVITY);
+
+            state.waitForDreamGone();
+            waitAndAssertTopResumedActivity(Components.TURN_SCREEN_ON_ACTIVITY,
+                    DEFAULT_DISPLAY, "TurnScreenOnActivity should resume through dream");
+        }
+    }
+
+    @Test
+    public void testStartTurnScreenOnAttrActivityDoesWake() {
+        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
+            launchActivity(Components.TURN_SCREEN_ON_ATTR_ACTIVITY);
+
+            state.waitForDreamGone();
+            waitAndAssertTopResumedActivity(Components.TURN_SCREEN_ON_ATTR_ACTIVITY,
+                    DEFAULT_DISPLAY, "TurnScreenOnAttrActivity should resume through dream");
+        }
+    }
+
+    @Test
+    public void testStartActivityOnKeyguardLocked() {
+        assumeTrue(supportsLockScreen());
+
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.setLockCredential();
+        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
+            launchActivityNoWait(Components.TEST_ACTIVITY);
+            waitAndAssertActivityState(Components.TEST_ACTIVITY, STATE_STOPPED,
+                "Activity must be started and stopped");
+            assertTrue(getIsDreaming());
+
+            launchActivity(Components.TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
+            state.waitForDreamGone();
+            waitAndAssertTopResumedActivity(Components.TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY,
+                    DEFAULT_DISPLAY, "TurnScreenOnShowOnLockActivity should resume through dream");
+            assertFalse(getIsDreaming());
+        }
+    }
+
+    private class DreamingState implements AutoCloseable {
+        public DreamingState(ComponentName dream) {
+            setActiveDream(dream);
+            startDream(dream);
+            waitAndAssertDreaming();
+        }
+
+        @Override
+        public void close() {
+            stopDream();
+        }
+
+        public void waitAndAssertDreaming() {
+            waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
+                    "Dream activity should be the top resumed activity");
+            mWmState.waitForValidState(mWmState.getHomeActivityName());
+            mWmState.assertVisibility(mWmState.getHomeActivityName(), false);
+            assertTrue(getIsDreaming());
+        }
+
+        public void waitForDreamGone() {
+            mWmState.waitForDreamGone();
+            assertFalse(getIsDreaming());
+        }
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
index 2e2e7de..977db0e 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
@@ -569,7 +569,7 @@
         mWmState.waitForKeyguardShowingAndNotOccluded();
         mWmState.waitForDisplayUnfrozen();
         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
-        mWmState.assertSanity();
+        mWmState.assertValidity();
         mWmState.assertHomeActivityVisible(false);
         mWmState.assertKeyguardShowingAndNotOccluded();
         // The {@link SHOW_WHEN_LOCKED_ACTIVITY} has gone because of the 'finish' broadcast.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
index 8717771..a8603db 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
@@ -109,7 +109,7 @@
             launchActivitiesInSplitScreen(
                     getLaunchActivityBuilder().setTargetActivity(BOTTOM_RIGHT_LAYOUT_ACTIVITY),
                     getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-            resizeDockedStack(1, 1, 1, 1);
+            resizePrimarySplitScreen(1, 1, 1, 1);
         }
         getDisplayAndWindowState(BOTTOM_RIGHT_LAYOUT_ACTIVITY, false);
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
index 2abb9de..8dea6b7 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
@@ -80,13 +80,7 @@
     }
 
     @Test
-    public void testNotPreferMinimalPostProcessingSimple() throws Exception {
-        launchMppActivity(MPP_ACTIVITY, NOT_PREFER_MPP);
-        assertDisplayRequestedMinimalPostProcessing(MPP_ACTIVITY, NOT_PREFER_MPP);
-    }
-
-    @Test
-    public void testAttrPreferMinimalPostProcessingDefault() throws Exception {
+    public void testPreferMinimalPostProcessingDefault() throws Exception {
         launchMppActivity(MPP_ACTIVITY, NOT_PREFER_MPP);
         assertDisplayRequestedMinimalPostProcessing(MPP_ACTIVITY, NOT_PREFER_MPP);
     }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
index 44bd353..bd839f4 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
@@ -278,7 +278,7 @@
         nonResizeableSession.takeCallbackHistory();
 
         // Try to move the non-resizeable activity to the top of stack on secondary display.
-        moveActivityToStackOrOnTop(NON_RESIZEABLE_ACTIVITY, externalFrontStackId);
+        moveActivityToRootTaskOrOnTop(NON_RESIZEABLE_ACTIVITY, externalFrontStackId);
         // Wait for a while to check that it will move.
         assertTrue("Non-resizeable activity should be moved",
                 mWmState.waitForWithAmState(
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
index b6fc590..3bbf2e0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
@@ -389,7 +389,7 @@
                 pair(newDisplay.mId, TEST_ACTIVITY));
 
         // Move activity from secondary display to primary.
-        moveActivityToStackOrOnTop(TEST_ACTIVITY, defaultDisplayStackId);
+        moveActivityToRootTaskOrOnTop(TEST_ACTIVITY, defaultDisplayStackId);
         waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
                 "Moved activity must be on top");
     }
@@ -480,7 +480,7 @@
                 .setWindowingMode(windowingMode)
                 .setActivityType(ACTIVITY_TYPE_STANDARD)
                 .build());
-        mWmState.assertSanity();
+        mWmState.assertValidity();
 
         // Check if the top activity is now back on primary display.
         mWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
@@ -763,7 +763,7 @@
         transitionActivitySession.launchTestActivityOnDisplaySync(StandardActivity.class,
                 DEFAULT_DISPLAY);
         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
-        mWmState.assertSanity();
+        mWmState.assertValidity();
         assertEquals(TRANSIT_TASK_OPEN,
                 mWmState.getDisplay(DEFAULT_DISPLAY).getLastTransition());
 
@@ -772,7 +772,7 @@
         launchActivityOnDisplayNoWait(TEST_ACTIVITY, newDisplay.mId);
         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
         mWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId);
-        mWmState.assertSanity();
+        mWmState.assertValidity();
 
         // Verify each display's last transition if is correct as expected.
         assertEquals(TRANSIT_TASK_CLOSE,
@@ -790,7 +790,7 @@
         // Launch TestActivity in virtual display & capture its transition state.
         launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
         mWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId);
-        mWmState.assertSanity();
+        mWmState.assertValidity();
         final String lastTranstionOnVirtualDisplay = mWmState
                 .getDisplay(newDisplay.mId).getLastTransition();
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index a03b782..e43352b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -44,6 +44,7 @@
 import static android.server.wm.app.Components.PipActivity.ACTION_FINISH;
 import static android.server.wm.app.Components.PipActivity.ACTION_MOVE_TO_BACK;
 import static android.server.wm.app.Components.PipActivity.ACTION_ON_PIP_REQUESTED;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ALLOW_AUTO_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
@@ -156,7 +157,7 @@
 
     @Test
     public void testMinimumDeviceSize() throws Exception {
-        mWmState.assertDeviceDefaultDisplaySize(
+        mWmState.assertDeviceDefaultDisplaySizeForMultiWindow(
                 "Devices supporting picture-in-picture must be larger than the default minimum"
                         + " task size");
     }
@@ -169,7 +170,7 @@
     }
 
     @Test
-    public void testMoveTopActivityToPinnedStack() throws Exception {
+    public void testMoveTopActivityToPinnedRootTask() throws Exception {
         pinnedStackTester(getAmStartCmd(PIP_ACTIVITY), PIP_ACTIVITY, PIP_ACTIVITY,
                 true /* moveTopToPinnedStack */, false /* isFocusable */);
     }
@@ -639,7 +640,7 @@
 
         // Remove the stack and ensure that the task is now in the fullscreen/freeform stack (when
         // no fullscreen/freeform stack existed before)
-        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
         assertPinnedStackStateOnMoveToBackStack(PIP_ACTIVITY,
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, windowingMode);
     }
@@ -657,7 +658,7 @@
 
         // Remove the stack and ensure that the task is placed in the fullscreen/freeform stack,
         // behind the top fullscreen/freeform activity
-        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
         assertPinnedStackStateOnMoveToBackStack(PIP_ACTIVITY,
                 testAppWindowingMode, ACTIVITY_TYPE_STANDARD, pipWindowingMode);
     }
@@ -676,7 +677,7 @@
 
         // Remove the stack and ensure that the task is placed on top of the hidden
         // fullscreen/freeform stack, but that the home stack is still focused
-        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
         assertPinnedStackStateOnMoveToBackStack(PIP_ACTIVITY,
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, windowingMode);
     }
@@ -684,7 +685,7 @@
     @Test
     public void testMovePipToBackWithNoFullscreenOrFreeformStack() throws Exception {
         // Start with a clean slate, remove all the stacks but home
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
 
         // Launch a pip activity
         launchActivity(PIP_ACTIVITY);
@@ -958,7 +959,7 @@
 
         // Dismiss it
         separateTestJournal();
-        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
         waitForExitPipToFullscreen(PIP_ACTIVITY);
         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
 
@@ -1027,10 +1028,7 @@
     }
 
     @Test
-    @FlakyTest(bugId=156314330)
     public void testFinishPipActivityWithTaskOverlay() throws Exception {
-        // Trigger PiP menu activity to properly lose focuse when going home
-        launchActivity(TEST_ACTIVITY);
         // Launch PiP activity
         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
         waitForEnterPip(PIP_ACTIVITY);
@@ -1218,6 +1216,21 @@
                 finalAppSize);
     }
 
+    @Test
+    public void testAutoPipAllowedBypassesExplicitEnterPip() {
+        // Launch a test activity so that we're not over home.
+        launchActivity(TEST_ACTIVITY);
+
+        // Launch the PIP activity and set its pip params to allow auto-pip.
+        launchActivity(PIP_ACTIVITY, EXTRA_ALLOW_AUTO_PIP, "true");
+        assertPinnedStackDoesNotExist();
+
+        // Go home and ensure that there is a pinned stack.
+        launchHomeActivity();
+        waitForEnterPip(PIP_ACTIVITY);
+        assertPinnedStackExists();
+    }
+
     /** Get app bounds in last applied configuration. */
     private Rect getAppBounds(ComponentName activityName) {
         final Configuration config = TestJournalContainer.get(activityName).extras
@@ -1491,7 +1504,7 @@
             final int stackId = mWmState.getStackIdByActivity(topActivityName);
 
             assertNotEquals(stackId, INVALID_STACK_ID);
-            moveTopActivityToPinnedStack(stackId);
+            moveTopActivityToPinnedRootTask(stackId);
         }
 
         mWmState.waitForValidState(new WaitForValidActivityState.Builder(topActivityName)
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
index 49a15f5..12aa2f0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
@@ -90,9 +90,12 @@
 
     @Test
     public void testMinimumDeviceSize() throws Exception {
-        mWmState.assertDeviceDefaultDisplaySize(
+        mWmState.assertDeviceDefaultDisplaySizeForMultiWindow(
                 "Devices supporting multi-window must be larger than the default minimum"
                         + " task size");
+        mWmState.assertDeviceDefaultDisplaySizeForSplitScreen(
+                "Devices supporting split-screen multi-window must be larger than the"
+                        + " default minimum display size.");
     }
 
 
@@ -178,7 +181,7 @@
     public void testLaunchToSideMultiWindowCallbacks() throws Exception {
         // Launch two activities in split-screen mode.
         launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(NO_RELAUNCH_ACTIVITY),
                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
         mWmState.assertContainsStack("Must contain fullscreen stack.",
                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
@@ -192,7 +195,7 @@
         if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
             // Exit split-screen mode and ensure we only get 1 multi-window mode changed callback.
             final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
-                    TEST_ACTIVITY);
+                    NO_RELAUNCH_ACTIVITY);
             assertEquals(1,
                     lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
         } else {
@@ -207,12 +210,13 @@
 
     @Test
     public void testNoUserLeaveHintOnMultiWindowModeChanged() throws Exception {
-        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
 
         // Move to docked stack.
         separateTestJournal();
-        setActivityTaskWindowingMode(TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(TEST_ACTIVITY);
+        setActivityTaskWindowingMode(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        ActivityLifecycleCounts lifecycleCounts =
+                waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
         assertEquals("mMultiWindowModeChangedCount",
                 1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
         assertEquals("mUserLeaveHintCount",
@@ -221,12 +225,12 @@
         // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
         // will come up.
         launchActivity(LAUNCHING_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
 
         // Move activity back to fullscreen stack.
         separateTestJournal();
-        setActivityTaskWindowingMode(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-        lifecycleCounts = waitForOnMultiWindowModeChanged(TEST_ACTIVITY);
+        setActivityTaskWindowingMode(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        lifecycleCounts = waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
         assertEquals("mMultiWindowModeChangedCount",
                 1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
         assertEquals("mUserLeaveHintCount",
@@ -436,7 +440,7 @@
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertSanity();
+        mWmState.assertValidity();
         mWmState.assertContainsStack("Must contain fullscreen stack.",
                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
         mWmState.assertContainsStack("Must contain docked stack.",
@@ -458,13 +462,13 @@
      * Verify split screen mode visibility after stack resize occurs.
      */
     @Test
-    public void testResizeDockedStack() throws Exception {
+    public void testResizePrimarySplitScreen() throws Exception {
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
         final Rect restoreDockBounds = mWmState.getStandardRootTaskByWindowingMode(
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) .getBounds();
-        resizeDockedStack(STACK_SIZE, STACK_SIZE, TASK_SIZE, TASK_SIZE);
+        resizePrimarySplitScreen(STACK_SIZE, STACK_SIZE, TASK_SIZE, TASK_SIZE);
         mWmState.computeState(
                 new WaitForValidActivityState(TEST_ACTIVITY),
                 new WaitForValidActivityState(DOCKED_ACTIVITY));
@@ -476,7 +480,7 @@
         mWmState.assertVisibility(TEST_ACTIVITY, true);
         int restoreW = restoreDockBounds.width();
         int restoreH = restoreDockBounds.height();
-        resizeDockedStack(restoreW, restoreH, restoreW, restoreH);
+        resizePrimarySplitScreen(restoreW, restoreH, restoreW, restoreH);
     }
 
     @Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
index b1a2a0d..3b13505 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
@@ -43,7 +43,6 @@
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.CommandSession.ActivitySession;
-import android.server.wm.app.Components;
 import android.server.wm.intent.Activities;
 
 
@@ -64,7 +63,7 @@
         final int[] allActivityTypes = Arrays.copyOf(ALL_ACTIVITY_TYPE_BUT_HOME,
                 ALL_ACTIVITY_TYPE_BUT_HOME.length + 1);
         allActivityTypes[allActivityTypes.length - 1] = WindowConfiguration.ACTIVITY_TYPE_HOME;
-        removeStacksWithActivityTypes(allActivityTypes);
+        removeRootTasksWithActivityTypes(allActivityTypes);
 
         waitAndAssertTopResumedActivity(defaultHome, DEFAULT_DISPLAY,
                 "Home activity should be restarted after force-finish");
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
index 55e6cc1..6644990 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -19,11 +19,18 @@
 import static android.server.wm.UiDeviceUtils.pressHomeButton;
 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.rule.ActivityTestRule;
+import org.junit.Before;
+import org.junit.Test;
 
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -32,35 +39,24 @@
 import android.content.pm.ConfigurationInfo;
 import android.content.pm.FeatureInfo;
 import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresDevice;
 import android.view.Gravity;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.SurfaceControl;
-import android.view.SurfaceHolder;
-import android.view.SurfaceHolder.Callback;
-import android.view.WindowInsets;
+import android.view.ViewTreeObserver;
 import android.view.WindowManager;
-import android.view.SurfaceControlViewHost;
-import android.widget.FrameLayout;
 import android.widget.Button;
-
-import android.view.SurfaceView;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import androidx.test.rule.ActivityTestRule;
+import android.widget.FrameLayout;
 
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.WidgetTestUtils;
 
-
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Before;
-import org.junit.Test;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Ensure end-to-end functionality of SurfaceControlViewHost.
@@ -121,16 +117,56 @@
     private void addViewToSurfaceView(SurfaceView sv, View v, int width, int height) {
         mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), sv.getHostToken());
 
-        sv.setChildSurfacePackage(mVr.getSurfacePackage());
 
         if (mEmbeddedLayoutParams == null) {
             mVr.setView(v, width, height);
         } else {
             mVr.setView(v, mEmbeddedLayoutParams);
         }
+
+        sv.setChildSurfacePackage(mVr.getSurfacePackage());
+
         assertEquals(v, mVr.getView());
     }
 
+    private void requestSurfaceViewFocus() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mSurfaceView.setFocusableInTouchMode(true);
+            mSurfaceView.requestFocusFromTouch();
+        });
+    }
+
+    private void assertWindowFocused(final View view, boolean hasWindowFocus) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
+                view, () -> {
+                    if (view.hasWindowFocus() == hasWindowFocus) {
+                        latch.countDown();
+                        return;
+                    }
+                    view.getViewTreeObserver().addOnWindowFocusChangeListener(
+                            new ViewTreeObserver.OnWindowFocusChangeListener() {
+                                @Override
+                                public void onWindowFocusChanged(boolean newFocusState) {
+                                    if (hasWindowFocus == newFocusState) {
+                                        view.getViewTreeObserver()
+                                                .removeOnWindowFocusChangeListener(this);
+                                        latch.countDown();
+                                    }
+                                }
+                            });
+                }
+        );
+
+        try {
+            if (!latch.await(3, TimeUnit.SECONDS)) {
+                fail();
+            }
+        } catch (InterruptedException e) {
+            fail();
+        }
+    }
+
     @Override
     public void surfaceCreated(SurfaceHolder holder) {
         addViewToSurfaceView(mSurfaceView, mEmbeddedView,
@@ -289,4 +325,42 @@
         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
         assertTrue(mClicked);
     }
+
+    @Test
+    public void testFocusable() throws Throwable {
+        mEmbeddedView = new Button(mActivity);
+        addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT);
+
+        // When surface view is focused, it should transfer focus to the embedded view.
+        requestSurfaceViewFocus();
+        assertWindowFocused(mEmbeddedView, true);
+        // assert host does not have focus
+        assertWindowFocused(mSurfaceView, false);
+
+        // When surface view is no longer focused, it should transfer focus back to the host window.
+        mActivityRule.runOnUiThread(() -> mSurfaceView.setFocusable(false));
+        assertWindowFocused(mEmbeddedView, false);
+        // assert host has focus
+        assertWindowFocused(mSurfaceView, true);
+    }
+
+    @Test
+    public void testNotFocusable() throws Throwable {
+        mEmbeddedView = new Button(mActivity);
+        addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT);
+        mEmbeddedLayoutParams = new WindowManager.LayoutParams(mEmbeddedViewWidth,
+                mEmbeddedViewHeight, WindowManager.LayoutParams.TYPE_APPLICATION, 0,
+                PixelFormat.OPAQUE);
+        mActivityRule.runOnUiThread(() -> {
+            mEmbeddedLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
+            mVr.relayout(mEmbeddedLayoutParams);
+        });
+
+        // When surface view is focused, nothing should happen since the embedded view is not
+        // focusable.
+        requestSurfaceViewFocus();
+        assertWindowFocused(mEmbeddedView, false);
+        // assert host has focus
+        assertWindowFocused(mSurfaceView, true);
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
index 67f51ab..767650d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
@@ -161,7 +161,7 @@
     public void testOnDetachedFromWindow() {
         assertFalse(mMockSurfaceView.isDetachedFromWindow());
         assertTrue(mMockSurfaceView.isShown());
-        CtsKeyEventUtil.sendKeys(mInstrumentation, mMockSurfaceView, KeyEvent.KEYCODE_BACK);
+        mActivityRule.finishActivity();
         PollingCheck.waitFor(() -> mMockSurfaceView.isDetachedFromWindow() &&
                 !mMockSurfaceView.isShown());
     }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java b/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java
new file mode 100644
index 0000000..f3aebf8
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityTaskManager;
+import android.content.ComponentName;
+import android.os.SystemClock;
+import android.platform.test.annotations.Postsubmit;
+import android.provider.Settings;
+import android.server.wm.annotation.Group3;
+import android.server.wm.app.Components;
+import android.server.wm.settings.SettingsSession;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.view.KeyEvent;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test scenarios where crash dialogs should not be shown if they are not supported.
+ *
+ * <p>Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:UnsupportedErrorDialogTests
+ */
+@Group3
+@Postsubmit
+public class UnsupportedErrorDialogTests extends ActivityManagerTestBase {
+    private final UiDevice mUiDevice = UiDevice.getInstance(mInstrumentation);
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        // These tests are for cases when error dialogs are not supported
+        Assume.assumeFalse(ActivityTaskManager.currentUiModeSupportsErrorDialogs(mContext));
+        super.setUp();
+        resetAppErrors();
+    }
+
+    /** Make sure the developer options apply correctly leading to the dialog being shown. */
+    @Test
+    public void testDevSettingOverride() {
+        try (SettingsSession<Integer> devDialogShow =
+                     secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
+             SettingsSession<Integer> showOnFirstCrash =
+                     globalIntSession(Settings.Global.SHOW_FIRST_CRASH_DIALOG);
+             SettingsSession<Integer> showOnFirstCrashDev =
+                     secureIntSession(Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION)) {
+            // set developer setting to show dialogs anyway
+            devDialogShow.set(1);
+
+            // enable only the regular option for showing the crash dialog after the first crash
+            showOnFirstCrash.set(1);
+            showOnFirstCrashDev.set(0);
+            launchActivityNoWait(Components.CRASHING_ACTIVITY);
+            findCrashDialogAndCloseApp();
+            ensureHomeFocused();
+
+            resetAppErrors();
+
+            // enable only the dev option for showing the crash dialog after the first crash
+            showOnFirstCrash.set(0);
+            showOnFirstCrashDev.set(1);
+            launchActivityNoWait(Components.CRASHING_ACTIVITY);
+            findCrashDialogAndCloseApp();
+            ensureHomeFocused();
+        }
+    }
+
+    /**
+     * Make sure the dialog appears if the dev option is set even if the user specifically
+     * set to suppress it.
+     */
+    @Test
+    public void testDevSettingPrecedence() {
+        try (SettingsSession<Integer> devDialogShow =
+                     globalIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
+             SettingsSession<Integer> userDialogHide =
+                     globalIntSession(Settings.Global.HIDE_ERROR_DIALOGS);
+             SettingsSession<Integer> showOnFirstCrash =
+                     globalIntSession(Settings.Global.SHOW_FIRST_CRASH_DIALOG)
+        ) {
+            devDialogShow.set(1);
+            showOnFirstCrash.set(1);
+            // I recon this would require pushing a configuration change
+            userDialogHide.set(1);
+
+            launchActivityNoWait(Components.CRASHING_ACTIVITY);
+            findCrashDialogAndCloseApp();
+            ensureHomeFocused();
+        }
+    }
+
+    /** Make sure the AppError dialog is not shown even if would have after the initial crash. */
+    @Test
+    public void testFirstCrashDialogNotShown() {
+        try (SettingsSession<Integer> devDialogShow =
+                     secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
+             SettingsSession<Integer> showOnFirstCrash =
+                     globalIntSession(Settings.Global.SHOW_FIRST_CRASH_DIALOG)) {
+            devDialogShow.set(0);
+            // enable showing crash dialog after first crash
+            showOnFirstCrash.set(1);
+
+            launchActivityNoWait(Components.CRASHING_ACTIVITY);
+
+            ensureNoCrashDialog(Components.CRASHING_ACTIVITY);
+            ensureHomeFocused();
+        }
+    }
+
+    /** Ensure the AppError dialog is not shown even after multiple crashes. */
+    @Test
+    public void testRepeatedCrashDialogNotShown() {
+        try (SettingsSession<Integer> devDialogShow =
+                     secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
+             SettingsSession<Integer> showOnFirstCrash =
+                     globalIntSession("show_first_crash_dialog");
+             SettingsSession<Integer> showOnFirstCrashDev =
+                     secureIntSession("show_first_crash_dialog_dev_option")) {
+            // disable all overrides
+            devDialogShow.set(0);
+            showOnFirstCrash.set(0);
+            showOnFirstCrashDev.set(0);
+
+            // repeatedly crash the app without resetting AppErrors
+            for (int i = 0; i < 5; i++) {
+                launchActivityNoWait(Components.CRASHING_ACTIVITY);
+                ensureNoCrashDialog(Components.CRASHING_ACTIVITY);
+            }
+            ensureHomeFocused();
+        }
+    }
+
+    /** Ensure the ANR dialog is also not shown. */
+    @Test
+    public void testAnrIsNotShown() {
+        // leave the settings at their defaults
+        // launch non responsive app
+        executeShellCommand(getAmStartCmd(Components.UNRESPONSIVE_ACTIVITY) + " --ei "
+                + Components.UnresponsiveActivity.EXTRA_ON_CREATE_DELAY_MS + " 30000");
+        // wait for app to be focused
+        mWmState.waitAndAssertAppFocus(Components.UNRESPONSIVE_ACTIVITY.getPackageName(),
+                2_000 /* waitTime */);
+        // wait for input manager to get the new focus app
+        SystemClock.sleep(500);
+        injectKey(KeyEvent.KEYCODE_BACK, false /* longPress */, false /* sync */);
+        ensureNoCrashDialog(Components.UNRESPONSIVE_ACTIVITY);
+        ensureHomeFocused();
+    }
+
+    private void findCrashDialogAndCloseApp() {
+        UiObject2 closeAppButton = findCloseButton();
+        assertNotNull("Could not find crash dialog!", closeAppButton);
+        closeAppButton.click();
+    }
+
+    private void ensureNoCrashDialog(ComponentName activity) {
+        UiObject2 closeButton = findCloseButton();
+        if (closeButton != null) {
+            closeButton.click();
+            fail("An unexpected crash dialog appeared!");
+        }
+        final int numWindows = mWmState.getWindowsByPackageName(activity.getPackageName()).size();
+        assertEquals(0, numWindows);
+    }
+
+    private void ensureHomeFocused() {
+        mWmState.computeState();
+        mWmState.assertFocusedActivity("The home activity should be visible!",
+                mWmState.getHomeActivityName());
+    }
+
+    /** Attempt to find the close button of a crash or ANR dialog in at most 2 seconds. */
+    private UiObject2 findCloseButton() {
+        return mUiDevice.wait(
+                Until.findObject(By.res("android:id/aerr_close")),
+                2_000);
+    }
+
+    private void resetAppErrors() {
+        SystemUtil.runWithShellPermissionIdentity(mAm::resetAppErrors,
+                android.Manifest.permission.RESET_APP_ERRORS);
+    }
+
+    private SettingsSession<Integer> globalIntSession(String settingName) {
+        return new SettingsSession<>(
+                Settings.Global.getUriFor(settingName),
+                Settings.Global::getInt, Settings.Global::putInt);
+    }
+
+    private SettingsSession<Integer> secureIntSession(String settingName) {
+        return new SettingsSession<>(
+                Settings.Secure.getUriFor(settingName),
+                Settings.Secure::getInt, Settings.Secure::putInt);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
index b6cc9b0..29383eb 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
@@ -60,7 +60,6 @@
 import android.view.WindowManager.LayoutParams;
 
 import androidx.annotation.NonNull;
-import androidx.test.filters.FlakyTest;
 
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -225,7 +224,6 @@
      * - The window which lost top-focus can be notified about pointer-capture lost.
      */
     @Test
-    @FlakyTest(bugId = 135574991)
     public void testPointerCapture() {
         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
                 DEFAULT_DISPLAY);
@@ -253,6 +251,31 @@
     }
 
     /**
+     * Pointer capture could be requested after activity regains focus.
+     */
+    @Test
+    public void testPointerCaptureWhenFocus() {
+        final AutoEngagePointerCaptureActivity primaryActivity =
+                startActivity(AutoEngagePointerCaptureActivity.class, DEFAULT_DISPLAY);
+
+        // Assert primary activity can have pointer capture before we have multiple focused windows.
+        primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
+
+        assumeTrue(supportsMultiDisplay());
+        final SecondaryActivity secondaryActivity =
+                createManagedInvisibleDisplaySession().startActivityAndFocus();
+
+        primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
+        // Assert primary activity lost pointer capture when it is not top focused.
+        primaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
+        secondaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
+
+        tapOn(primaryActivity);
+        primaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */);
+        primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
+    }
+
+    /**
      * Test if the focused window can still have focus after it is moved to another display.
      */
     @Test
@@ -476,6 +499,16 @@
         }
     }
 
+    public static class AutoEngagePointerCaptureActivity extends InputTargetActivity {
+        @Override
+        public void onWindowFocusChanged(boolean hasFocus) {
+            if (hasFocus) {
+                requestPointerCapture();
+            }
+            super.onWindowFocusChanged(hasFocus);
+        }
+    }
+
     private InvisibleVirtualDisplaySession createManagedInvisibleDisplaySession() {
         return mObjectTracker.manage(
                 new InvisibleVirtualDisplaySession(getInstrumentation().getTargetContext()));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
index ce0a51e..0847411 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
@@ -20,26 +20,33 @@
 import static android.server.wm.BarTestUtils.assumeHasStatusBar;
 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.app.Service;
 import android.content.ContentResolver;
 import android.content.Intent;
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.server.wm.app.Components;
 import android.server.wm.settings.SettingsSession;
+import android.util.ArraySet;
 import android.view.Gravity;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -57,6 +64,7 @@
 
 import java.util.ArrayList;
 import java.util.Random;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -99,7 +107,6 @@
         mActivity.getDisplay().getSize(displaySize);
 
         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
-        mClickCount = 0;
 
         // Set up window.
         mActivityRule.runOnUiThread(() -> {
@@ -144,26 +151,25 @@
     }
 
     @Test
-    public void testFilterTouchesWhenObscured() throws Throwable {
+    public void testTouchModalWindow() throws Throwable {
         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
-        mClickCount = 0;
 
-        // Set up window.
+        // Set up 2 touch modal windows, expect the last one will receive all touch events.
         mActivityRule.runOnUiThread(() -> {
             mView = new View(mActivity);
             p.width = 20;
             p.height = 20;
-            p.gravity = Gravity.LEFT | Gravity.TOP;
+            p.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
             mView.setFilterTouchesWhenObscured(true);
             mView.setOnClickListener((v) -> {
                 mClickCount++;
             });
             mActivity.addWindow(mView, p);
 
-            View viewOverlap = new View(mActivity);
-            p.gravity = Gravity.RIGHT | Gravity.TOP;
+            View view2 = new View(mActivity);
+            p.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
             p.type = WindowManager.LayoutParams.TYPE_APPLICATION;
-            mActivity.addWindow(viewOverlap, p);
+            mActivity.addWindow(view2, p);
         });
         mInstrumentation.waitForIdleSync();
 
@@ -171,18 +177,102 @@
         assertEquals(0, mClickCount);
     }
 
+    // If a window is obscured by another window from the same app, touches should still get
+    // delivered to the bottom window, and the FLAG_WINDOW_IS_OBSCURED should not be set.
     @Test
-    public void testOverlapWindow() throws Throwable {
+    public void testFilterTouchesWhenObscuredByWindowFromSameUid() throws Throwable {
         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
-        mClickCount = 0;
+
+        // Set up a touchable window.
+        mActivityRule.runOnUiThread(() -> {
+            mView = new View(mActivity);
+            p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+            p.width = 100;
+            p.height = 100;
+            p.gravity = Gravity.CENTER;
+            mView.setFilterTouchesWhenObscured(true);
+            mView.setOnClickListener((v) -> {
+                mClickCount++;
+            });
+            mView.setOnTouchListener((v, ev) -> {
+                assertEquals((ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED), 0);
+                return false;
+            });
+            mActivity.addWindow(mView, p);
+
+            // Set up an overlap window, use same process.
+            View overlay = new View(mActivity);
+            p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN | FLAG_NOT_TOUCHABLE;
+            p.width = 100;
+            p.height = 100;
+            p.gravity = Gravity.CENTER;
+            p.type = WindowManager.LayoutParams.TYPE_APPLICATION;
+            mActivity.addWindow(overlay, p);
+        });
+        mInstrumentation.waitForIdleSync();
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+        assertEquals(1, mClickCount);
+    }
+
+    @Test
+    public void testFilterTouchesWhenObscuredByWindowFromDifferentUid() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        final Intent intent = new Intent();
+        intent.setComponent(Components.OVERLAY_TEST_SERVICE);
+        final String windowName = "Test Overlay";
+        try {
+            // Set up a touchable window.
+            mActivityRule.runOnUiThread(() -> {
+                mView = new View(mActivity);
+                p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+                p.width = 100;
+                p.height = 100;
+                p.gravity = Gravity.CENTER;
+                mView.setFilterTouchesWhenObscured(true);
+                mView.setOnClickListener((v) -> {
+                    mClickCount++;
+                });
+                mView.setOnTouchListener((v, ev) -> {
+                    assertEquals((ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED),
+                            MotionEvent.FLAG_WINDOW_IS_OBSCURED);
+                    return false;
+                });
+                mActivity.addWindow(mView, p);
+
+                // Set up an overlap window from service, use different process.
+                intent.putExtra(Components.OverlayTestService.EXTRA_WINDOW_NAME, windowName);
+                mActivity.startForegroundService(intent);
+            });
+            mInstrumentation.waitForIdleSync();
+
+            final WindowManagerStateHelper wmState = new WindowManagerStateHelper();
+            wmState.waitForWithAmState(state -> {
+                return state.isWindowSurfaceShown(windowName);
+            }, windowName + "'s surface is appeared");
+
+            CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+            assertEquals(0, mClickCount);
+        } finally {
+            mActivity.stopService(intent);
+        }
+    }
+
+    @Test
+    public void testTrustedOverlapWindow() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
         try (final PointerLocationSession session = new PointerLocationSession()) {
             session.set(true);
+            session.waitForReady(mActivity.getDisplayId());
+
             // Set up window.
             mActivityRule.runOnUiThread(() -> {
                 mView = new View(mActivity);
                 p.width = 20;
                 p.height = 20;
-                p.gravity = Gravity.LEFT | Gravity.TOP;
+                p.gravity = Gravity.CENTER;
                 mView.setFilterTouchesWhenObscured(true);
                 mView.setOnClickListener((v) -> {
                     mClickCount++;
@@ -201,7 +291,6 @@
     public void testWindowBecomesUnTouchable() throws Throwable {
         final WindowManager wm = mActivity.getWindowManager();
         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
-        mClickCount = 0;
 
         final View viewOverlap = new View(mActivity);
 
@@ -210,7 +299,7 @@
             mView = new View(mActivity);
             p.width = 20;
             p.height = 20;
-            p.gravity = Gravity.LEFT | Gravity.TOP;
+            p.gravity = Gravity.CENTER;
             mView.setOnClickListener((v) -> {
                 mClickCount++;
             });
@@ -218,7 +307,7 @@
 
             p.width = 100;
             p.height = 100;
-            p.gravity = Gravity.LEFT | Gravity.TOP;
+            p.gravity = Gravity.CENTER;
             p.type = WindowManager.LayoutParams.TYPE_APPLICATION;
             mActivity.addWindow(viewOverlap, p);
         });
@@ -238,6 +327,61 @@
     }
 
     @Test
+    public void testTapInsideUntouchableWindowResultInOutsideTouches() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        final Set<MotionEvent> events = new ArraySet<>();
+        mActivityRule.runOnUiThread(() -> {
+            mView = new View(mActivity);
+            p.width = 20;
+            p.height = 20;
+            p.gravity = Gravity.CENTER;
+            p.flags = FLAG_NOT_TOUCHABLE | FLAG_WATCH_OUTSIDE_TOUCH;
+            mView.setOnTouchListener((v, e) -> {
+                // Copying to make sure we are not dealing with a reused object
+                events.add(MotionEvent.obtain(e));
+                return false;
+            });
+            mActivity.addWindow(mView, p);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+        assertEquals(1, events.size());
+        MotionEvent event = events.iterator().next();
+        assertEquals(MotionEvent.ACTION_OUTSIDE, event.getAction());
+    }
+
+    @Test
+    public void testTapOutsideUntouchableWindowResultInOutsideTouches() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        Set<MotionEvent> events = new ArraySet<>();
+        int size = 20;
+        mActivityRule.runOnUiThread(() -> {
+            mView = new View(mActivity);
+            p.width = size;
+            p.height = size;
+            p.gravity = Gravity.CENTER;
+            p.flags = FLAG_NOT_TOUCHABLE | FLAG_WATCH_OUTSIDE_TOUCH;
+            mView.setOnTouchListener((v, e) -> {
+                // Copying to make sure we are not dealing with a reused object
+                events.add(MotionEvent.obtain(e));
+                return false;
+            });
+            mActivity.addWindow(mView, p);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        CtsTouchUtils.emulateTapOnView(mInstrumentation, mActivityRule, mView, size + 5, size + 5);
+
+        assertEquals(1, events.size());
+        MotionEvent event = events.iterator().next();
+        assertEquals(MotionEvent.ACTION_OUTSIDE, event.getAction());
+    }
+
+    @Test
     public void testInjectToStatusBar() {
         // Try to inject event to status bar.
         assumeHasStatusBar(mActivityRule);
@@ -337,5 +481,14 @@
                 return false;
             }
         }
+
+        // Wait until pointer location surface shown.
+        static void waitForReady(int displayId) {
+            final WindowManagerStateHelper wmState = new WindowManagerStateHelper();
+            final String windowName = "PointerLocation - display " + displayId;
+            wmState.waitForWithAmState(state -> {
+                return state.isWindowSurfaceShown(windowName);
+            }, windowName + "'s surface is appeared");
+        }
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
index aca3a97..2abbd46 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
@@ -24,6 +24,7 @@
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -40,6 +41,7 @@
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
@@ -547,6 +549,88 @@
 
     }
 
+    @Test
+    public void testDispatchApplyWindowInsetsCount_systemBars() throws InterruptedException {
+        final TestActivity activity = startActivity(TestActivity.class);
+        final View rootView = activity.getWindow().getDecorView();
+        getInstrumentation().waitForIdleSync();
+
+        // Assume we have at least one visible system bar.
+        assumeTrue(rootView.getRootWindowInsets().isVisible(statusBars())
+                || rootView.getRootWindowInsets().isVisible(navigationBars()));
+
+        final int[] dispatchApplyWindowInsetsCount = {0};
+        rootView.setOnApplyWindowInsetsListener((v, insets) -> {
+            dispatchApplyWindowInsetsCount[0]++;
+            return v.onApplyWindowInsets(insets);
+        });
+
+        // One show-system-bar call...
+        ANIMATION_CALLBACK.reset();
+        getInstrumentation().runOnMainSync(() -> {
+            rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
+            rootView.getWindowInsetsController().hide(systemBars());
+        });
+        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+
+        // ... should only trigger one dispatchApplyWindowInsets
+        assertEquals(1, dispatchApplyWindowInsetsCount[0]);
+
+        // One hide-system-bar call...
+        dispatchApplyWindowInsetsCount[0] = 0;
+        ANIMATION_CALLBACK.reset();
+        getInstrumentation().runOnMainSync(() -> {
+            rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
+            rootView.getWindowInsetsController().show(systemBars());
+        });
+        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+
+        // ... should only trigger one dispatchApplyWindowInsets
+        assertEquals(1, dispatchApplyWindowInsetsCount[0]);
+    }
+
+    @Test
+    public void testDispatchApplyWindowInsetsCount_ime() throws Exception {
+        assumeThat(MockImeSession.getUnavailabilityReason(getInstrumentation().getContext()),
+                nullValue());
+
+        try (MockImeSession imeSession = MockImeSession.create(getInstrumentation().getContext(),
+                getInstrumentation().getUiAutomation(), new ImeSettings.Builder())) {
+            final TestActivity activity = startActivity(TestActivity.class);
+            final View rootView = activity.getWindow().getDecorView();
+            getInstrumentation().waitForIdleSync();
+
+            final int[] dispatchApplyWindowInsetsCount = {0};
+            rootView.setOnApplyWindowInsetsListener((v, insets) -> {
+                dispatchApplyWindowInsetsCount[0]++;
+                return v.onApplyWindowInsets(insets);
+            });
+
+            // One show-ime call...
+            ANIMATION_CALLBACK.reset();
+            getInstrumentation().runOnMainSync(() -> {
+                rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
+                rootView.getWindowInsetsController().show(ime());
+            });
+            ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+
+            // ... should only trigger one dispatchApplyWindowInsets
+            assertEquals(1, dispatchApplyWindowInsetsCount[0]);
+
+            // One hide-ime call...
+            dispatchApplyWindowInsetsCount[0] = 0;
+            ANIMATION_CALLBACK.reset();
+            getInstrumentation().runOnMainSync(() -> {
+                rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
+                rootView.getWindowInsetsController().hide(ime());
+            });
+            ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+
+            // ... should only trigger one dispatchApplyWindowInsets
+            assertEquals(1, dispatchApplyWindowInsetsCount[0]);
+        }
+    }
+
     private static void broadcastCloseSystemDialogs() {
         executeShellCommand(AM_BROADCAST_CLOSE_SYSTEM_DIALOGS);
     }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTests.java
index 05d80a0..6d29b88 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTests.java
@@ -24,6 +24,7 @@
 import static org.junit.Assume.assumeFalse;
 
 import android.app.Activity;
+import android.content.ComponentName;
 import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -77,7 +78,7 @@
     }
 
     @Test
-    public void testMetricsMatchesDisplay() {
+    public void testMetricsMatchesDisplayArea() {
         final MetricsActivity activity = mMetricsActivity.launchActivity(null);
         activity.waitForLayout();
 
@@ -94,14 +95,11 @@
                 displaySize.y, bounds.height());
 
         // Check max window size
-        final Point realDisplaySize = new Point();
-        display.getRealSize(realDisplaySize);
+        final Rect tdaBounds = getTaskDisplayAreaBounds(activity.getComponentName());
         final WindowMetrics maxWindowMetrics = activity.getWindowManager()
                 .getMaximumWindowMetrics();
-        assertEquals("Reported real display width must match max window size",
-                realDisplaySize.x, maxWindowMetrics.getBounds().width());
-        assertEquals("Reported real display height must match max window size",
-                realDisplaySize.y, maxWindowMetrics.getBounds().height());
+        assertEquals("Display area bounds must match max window size",
+                tdaBounds, maxWindowMetrics.getBounds());
     }
 
     private static Rect getLegacyBounds(WindowMetrics windowMetrics) {
@@ -121,6 +119,12 @@
         return new Rect(left, top, right, bottom);
     }
 
+    private Rect getTaskDisplayAreaBounds(ComponentName activityName) {
+        mWmState.computeState();
+        WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName);
+        return tda.mFullConfiguration.windowConfiguration.getBounds();
+    }
+
     public static class MetricsActivity extends Activity implements View.OnLayoutChangeListener {
 
         private final CountDownLatch mLayoutLatch = new CountDownLatch(1);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
new file mode 100644
index 0000000..2af0a5f
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
@@ -0,0 +1,837 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW;
+import static android.server.wm.UiDeviceUtils.pressUnlockButton;
+import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.input.InputManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.wm.overlay.Components;
+import android.util.ArraySet;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+import com.android.compatibility.common.util.CtsTouchUtils;
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Presubmit
+public class WindowUntrustedTouchTest {
+    private static final float MAXIMUM_OBSCURING_OPACITY = .8f;
+    private static final long TOUCH_TIME_OUT_MS = 1000L;
+    private static final long PROCESS_RESPONSE_TIME_OUT_MS = 1000L;
+    private static final int OVERLAY_COLOR = 0xFFFF0000;
+    private static final int ACTIVITY_COLOR = 0xFFFFFFFF;
+
+    private static final int FEATURE_MODE_DISABLED = 0;
+    private static final int FEATURE_MODE_PERMISSIVE = 1;
+    private static final int FEATURE_MODE_BLOCK = 2;
+
+    private static final String APP_SELF =
+            WindowUntrustedTouchTest.class.getPackage().getName() + ".cts";
+    private static final String APP_A =
+            android.server.wm.second.Components.class.getPackage().getName();
+    private static final String APP_B =
+            android.server.wm.third.Components.class.getPackage().getName();
+    private static final String WINDOW_1 = "W1";
+    private static final String WINDOW_2 = "W2";
+
+    private static final String[] APPS = {APP_A, APP_B};
+    private static final String APP_COMPAT_ENABLE_CHANGE =
+            "am compat enable " + InputManager.BLOCK_UNTRUSTED_TOUCHES + " ";
+    private static final String APP_COMPAT_RESET_CHANGE =
+            "am compat reset " + InputManager.BLOCK_UNTRUSTED_TOUCHES + " ";
+
+    private static final String SETTING_MAXIMUM_OBSCURING_OPACITY =
+            "maximum_obscuring_opacity_for_touch";
+
+
+    private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
+    private final IBinder mPongCallback = new PongCallback();
+    private Instrumentation mInstrumentation;
+    private Context mContext;
+    private ContentResolver mContentResolver;
+    private InputManager mInputManager;
+    private WindowManager mWindowManager;
+    private ActivityManager mActivityManager;
+    private TestActivity mActivity;
+    private View mContainer;
+    private float mPreviousTouchOpacity;
+    private int mPreviousMode;
+    private int mPreviousSawAppOp;
+    private volatile ConditionVariable mPongReceived;
+    private final Set<String> mSawWindowsAdded = new ArraySet<>();
+    private final AtomicInteger mTouchesReceived = new AtomicInteger(0);
+
+    /** Can only be accessed from the main thread */
+    private final Set<View> mSawViewsAdded = new ArraySet<>();
+    private Toast mToast;
+
+    @Rule
+    public ActivityTestRule<TestActivity> rule = new ActivityTestRule<>(TestActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = rule.getActivity();
+        mContainer = mActivity.view;
+        mContainer.setOnTouchListener(this::onTouchEvent);
+        mInstrumentation = getInstrumentation();
+        mContext = mInstrumentation.getContext();
+        mContentResolver = mContext.getContentResolver();
+        mInputManager = mContext.getSystemService(InputManager.class);
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+        mActivityManager = mContext.getSystemService(ActivityManager.class);
+
+        mPreviousSawAppOp = AppOpsUtils.getOpMode(APP_SELF, OPSTR_SYSTEM_ALERT_WINDOW);
+        AppOpsUtils.setOpMode(APP_SELF, OPSTR_SYSTEM_ALERT_WINDOW, MODE_ALLOWED);
+        mPreviousTouchOpacity = setMaximumObscuringOpacityForTouch(MAXIMUM_OBSCURING_OPACITY);
+        mPreviousMode = setBlockUntrustedTouchesMode(FEATURE_MODE_BLOCK);
+        for (String app : APPS) {
+            SystemUtil.runShellCommand(mInstrumentation, APP_COMPAT_ENABLE_CHANGE + app);
+        }
+        for (String app : APPS) {
+            // Previous app-compat command restarts the processes, we don't want the process initial
+            // delay in broadcast response result in windows not appearing on time, hence we ping
+            // the process here and wait for its response before we kick off the tests.
+            ping(app);
+        }
+        pressWakeupButton();
+        pressUnlockButton();
+    }
+
+    @After
+    public void tearDown() throws Throwable {
+        mTouchesReceived.set(0);
+        removeOverlays();
+        for (String app : APPS) {
+            stopPackage(app);
+            SystemUtil.runShellCommand(mInstrumentation, APP_COMPAT_RESET_CHANGE + app);
+        }
+        setBlockUntrustedTouchesMode(mPreviousMode);
+        setMaximumObscuringOpacityForTouch(mPreviousTouchOpacity);
+        AppOpsUtils.setOpMode(APP_SELF, OPSTR_SYSTEM_ALERT_WINDOW, mPreviousSawAppOp);
+    }
+
+    @Test
+    public void testWhenFeatureInDisabledModeAndActivityWindowAbove_allowsTouch()
+            throws Throwable {
+        setBlockUntrustedTouchesMode(FEATURE_MODE_DISABLED);
+        addActivityOverlay(APP_A, /* opacity */ .9f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenFeatureInPermissiveModeAndActivityWindowAbove_allowsTouch()
+            throws Throwable {
+        setBlockUntrustedTouchesMode(FEATURE_MODE_PERMISSIVE);
+        addActivityOverlay(APP_A, /* opacity */ .9f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenFeatureInBlockModeAndActivityWindowAbove_blocksTouch()
+            throws Throwable {
+        setBlockUntrustedTouchesMode(FEATURE_MODE_BLOCK);
+        addActivityOverlay(APP_A, /* opacity */ .9f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testAfterSettingThreshold_returnsThresholdSet()
+            throws Throwable {
+        float threshold = .123f;
+        setMaximumObscuringOpacityForTouch(threshold);
+
+        assertEquals(threshold, mInputManager.getMaximumObscuringOpacityForTouch(mContext));
+    }
+
+    @Test
+    public void testAfterSettingFeatureMode_returnsModeSet()
+            throws Throwable {
+        // Make sure the previous mode is different
+        setBlockUntrustedTouchesMode(FEATURE_MODE_BLOCK);
+        assertEquals(FEATURE_MODE_BLOCK, mInputManager.getBlockUntrustedTouchesMode(mContext));
+        setBlockUntrustedTouchesMode(FEATURE_MODE_PERMISSIVE);
+
+        assertEquals(FEATURE_MODE_PERMISSIVE, mInputManager.getBlockUntrustedTouchesMode(mContext));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testAfterSettingThresholdLessThan0_throws() throws Throwable {
+        setMaximumObscuringOpacityForTouch(-.5f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testAfterSettingThresholdGreaterThan1_throws() throws Throwable {
+        setMaximumObscuringOpacityForTouch(1.5f);
+    }
+
+    /** This is testing what happens if setting is overridden manually */
+    @Test
+    public void testAfterSettingThresholdGreaterThan1ViaSettings_previousThresholdIsUsed()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(.8f);
+        assertEquals(.8f, mInputManager.getMaximumObscuringOpacityForTouch(mContext));
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.Global.putFloat(mContentResolver, SETTING_MAXIMUM_OBSCURING_OPACITY, 1.5f);
+        });
+        addSawOverlay(APP_A, WINDOW_1, 9.f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        // Blocks because it's using previous maximum of .8
+        assertTouchNotReceived();
+    }
+
+    /** This is testing what happens if setting is overridden manually */
+    @Test
+    public void testAfterSettingThresholdLessThan0ViaSettings_previousThresholdIsUsed()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(.8f);
+        assertEquals(.8f, mInputManager.getMaximumObscuringOpacityForTouch(mContext));
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.Global.putFloat(mContentResolver, SETTING_MAXIMUM_OBSCURING_OPACITY, -.5f);
+        });
+        addSawOverlay(APP_A, WINDOW_1, .7f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        // Allows because it's using previous maximum of .8
+        assertTouchReceived();
+    }
+
+    /** SAWs */
+
+    @Test
+    public void testWhenOneSawWindowAboveThreshold_blocksTouch() throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .9f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneSawWindowBelowThreshold_allowsTouch() throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .7f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenOneSawWindowAtThreshold_allowsTouch() throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, MAXIMUM_OBSCURING_OPACITY);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenTwoSawWindowsFromSameAppTogetherBelowThreshold_allowsTouch()
+            throws Throwable {
+        // Resulting opacity = 1 - (1 - 0.5)*(1 - 0.5) = .75
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addSawOverlay(APP_A, WINDOW_2, .5f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenTwoSawWindowsFromSameAppTogetherAboveThreshold_blocksTouch()
+            throws Throwable {
+        // Resulting opacity = 1 - (1 - 0.7)*(1 - 0.7) = .91
+        addSawOverlay(APP_A, WINDOW_1, .7f);
+        addSawOverlay(APP_A, WINDOW_2, .7f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenTwoSawWindowsFromDifferentAppsEachBelowThreshold_allowsTouch()
+            throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .7f);
+        addSawOverlay(APP_B, WINDOW_2, .7f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenOneSawWindowAboveThresholdAndSelfSawWindow_blocksTouch()
+            throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .9f);
+        addSawOverlay(APP_SELF, WINDOW_1, .7f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneSawWindowBelowThresholdAndSelfSawWindow_allowsTouch()
+            throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .7f);
+        addSawOverlay(APP_SELF, WINDOW_1, .7f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenTwoSawWindowsTogetherBelowThresholdAndSelfSawWindow_allowsTouch()
+            throws Throwable {
+        // Resulting opacity for A = 1 - (1 - 0.5)*(1 - 0.5) = .75
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addSawOverlay(APP_SELF, WINDOW_1, .7f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenThresholdIs0AndSawWindowAtThreshold_allowsTouch()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(0);
+        addSawOverlay(APP_A, WINDOW_1, 0);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenThresholdIs0AndSawWindowAboveThreshold_blocksTouch()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(0);
+        addSawOverlay(APP_A, WINDOW_1, .1f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenThresholdIs1AndSawWindowAtThreshold_allowsTouch()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(1);
+        addSawOverlay(APP_A, WINDOW_1, 1);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenThresholdIs1AndSawWindowBelowThreshold_allowsTouch()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(1);
+        addSawOverlay(APP_A, WINDOW_1, .9f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    /** Activity windows */
+
+    @Test
+    public void testWhenOneActivityWindowBelowThreshold_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAboveThreshold_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .9f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneSelfActivityWindow_allowsTouch() throws Throwable {
+        addActivityOverlay(APP_SELF, /* opacity */ .9f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenTwoActivityWindowsFromDifferentAppsTogetherBelowThreshold_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .7f);
+        addActivityOverlay(APP_B, /* opacity */ .7f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAndOneSawWindowTogetherBelowThreshold_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAndOneSelfCustomToastWindow_blocksTouch()
+            throws Throwable {
+        // Toast has to be before otherwise it would be blocked from background
+        addCustomToastOverlay(APP_SELF);
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAndOneSelfSawWindow_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+        addSawOverlay(APP_SELF, WINDOW_1, .5f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAndOneSawWindowBelowThreshold_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAndOneSawWindowBelowThresholdFromDifferentApp_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+        addSawOverlay(APP_B, WINDOW_1, .5f);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    /** Toast windows */
+
+    @Test
+    public void testWhenOneCustomToastWindow_blocksTouch() throws Throwable {
+        addCustomToastOverlay(APP_A);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneSelfCustomToastWindow_allowsTouch() throws Throwable {
+        addCustomToastOverlay(APP_SELF);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenOneCustomToastWindowAndOneSelfSawWindow_blocksTouch()
+            throws Throwable {
+        addSawOverlay(APP_SELF, WINDOW_1, .9f);
+        addCustomToastOverlay(APP_A);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneCustomToastWindowAndOneSawWindowBelowThreshold_blocksTouch()
+            throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addCustomToastOverlay(APP_A);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneCustomToastWindowAndOneSawWindowBelowThresholdFromDifferentApp_blocksTouch()
+            throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addCustomToastOverlay(APP_B);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneSelfCustomToastWindowOneSelfActivityWindowAndOneSawBelowThreshold_allowsTouch()
+            throws Throwable {
+        addActivityOverlay(APP_SELF, /* opacity */ .9f);
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addCustomToastOverlay(APP_SELF);
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+
+        assertTouchReceived();
+    }
+
+    private boolean onTouchEvent(View view, MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            mTouchesReceived.incrementAndGet();
+        }
+        return true;
+    }
+
+    private void assertTouchReceived() throws Exception {
+        PollingCheck.check("Expected touch not received on time",
+                TOUCH_TIME_OUT_MS, () -> mTouchesReceived.get() >= 1);
+        int touches = mTouchesReceived.get();
+        assertWithMessage("Number of touches reset unexpectedly").that(touches).isAtLeast(1);
+        assertWithMessage("Got too many touches").that(touches).isAtMost(1);
+    }
+
+    private void assertTouchNotReceived() throws Throwable {
+        // Fail as soon as possible
+        assertWithMessage("Unexpected touch received").that(mTouchesReceived.get()).isEqualTo(0);
+        removeOverlays();
+        // Fail as soon as possible
+        assertWithMessage("Unexpected touch received").that(mTouchesReceived.get()).isEqualTo(0);
+        // Now we sent a sentinel touch and wait to get it back
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, rule, mContainer);
+        PollingCheck.check("Expected sentinel touch not received",
+                TOUCH_TIME_OUT_MS, () -> mTouchesReceived.get() >= 1);
+        int touches = mTouchesReceived.get();
+        assertWithMessage("Number of touches reset unexpectedly").that(touches).isAtLeast(1);
+        assertWithMessage("Unexpected touch received").that(touches).isAtMost(1);
+    }
+
+    private void addCustomToastOverlay(String packageName) {
+        if (packageName.equals(APP_SELF)) {
+            addMyCustomToastOverlay();
+        } else {
+            // We have to use an activity that will display the toast then finish itself because
+            // custom toasts cannot be posted from the background.
+            Intent intent = new Intent();
+            intent.setComponent(repackage(packageName, Components.ToastActivity.COMPONENT));
+            intent.putExtra(Components.ToastActivity.EXTRA_CUSTOM, true);
+            mActivity.startActivity(intent);
+        }
+        String message = "Toast from app " + packageName + " did not appear on time";
+        // TODO: WindowStateProto does not have package/UID information from the window, the current
+        // package test relies on the window name, which is not how toast windows are named. We
+        // should ideally incorporate that information in WindowStateProto and use here.
+        if (!mWmState.waitFor(message,
+                state -> !state.getMatchingWindowType(LayoutParams.TYPE_TOAST).isEmpty())) {
+            fail(message);
+        }
+    }
+
+    private void addMyCustomToastOverlay() {
+        mActivity.runOnUiThread(() -> {
+            mToast = new Toast(mContext);
+            View view = new View(mContext);
+            view.setBackgroundColor(OVERLAY_COLOR);
+            mToast.setView(view);
+            mToast.setGravity(Gravity.FILL, 0, 0);
+            mToast.setDuration(Toast.LENGTH_LONG);
+            mToast.show();
+        });
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private void removeMyCustomToastOverlay() {
+        mActivity.runOnUiThread(() -> {
+            if (mToast != null) {
+                mToast.cancel();
+                mToast = null;
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private void waitForNoToastOverlays(String message) {
+        if (!mWmState.waitFor(message,
+                state -> state.getMatchingWindowType(LayoutParams.TYPE_TOAST).isEmpty())) {
+            fail(message);
+        }
+    }
+
+    private void addActivityOverlay(String packageName, float opacity) {
+        ComponentName activityComponent = (packageName.equals(APP_SELF))
+                ? new ComponentName(mContext, OverlayActivity.class)
+                : repackage(packageName,Components.OverlayActivity.COMPONENT);
+        Intent intent = new Intent();
+        intent.setComponent(activityComponent);
+        intent.putExtra(Components.OverlayActivity.EXTRA_OPACITY, opacity);
+        mActivity.startActivity(intent);
+        String message = "Activity from app " + packageName + " did not appear on time";
+        String activity = ComponentNameUtils.getActivityName(activityComponent);
+        if (!mWmState.waitFor(message,
+                state -> activity.equals(state.getFocusedActivity())
+                        && state.hasActivityState(activityComponent, STATE_RESUMED))) {
+            fail(message);
+        }
+    }
+
+    private void removeActivityOverlays() {
+        Intent intent = new Intent(mContext, mActivity.getClass());
+        // Will clear any activity on top of it and it will become the new top
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        mActivity.startActivity(intent);
+    }
+
+    private void waitForNoActivityOverlays(String message) {
+        // Base activity focused means no activities on top
+        ComponentName component = mActivity.getComponentName();
+        String name = ComponentNameUtils.getActivityName(component);
+        if (!mWmState.waitFor(message,
+                state -> name.equals(state.getFocusedActivity())
+                        && state.hasActivityState(component, STATE_RESUMED))) {
+            fail(message);
+        }
+    }
+
+    private void addSawOverlay(String packageName, String windowSuffix, float opacity)
+            throws Throwable {
+        String name = packageName + "." + windowSuffix;
+        if (packageName.equals(APP_SELF)) {
+            addMySawOverlay(name, opacity);
+        } else {
+            Intent intent = new Intent(Components.ActionReceiver.ACTION_OVERLAY);
+            intent.setComponent(repackage(packageName, Components.ActionReceiver.COMPONENT));
+            intent.putExtra(Components.ActionReceiver.EXTRA_NAME, name);
+            intent.putExtra(Components.ActionReceiver.EXTRA_OPACITY, opacity);
+            mContext.sendBroadcast(intent);
+        }
+        mSawWindowsAdded.add(name);
+        String message = "Window " + name + " did not appear on time";
+        if (!mWmState.waitFor(message,
+                state -> state.isWindowVisible(name) && state.isWindowSurfaceShown(name))) {
+            fail(message);
+        }
+    }
+
+    private void addMySawOverlay(String name, float opacity) throws Throwable {
+        rule.runOnUiThread(() -> {
+            View view = new View(mContext);
+            view.setBackgroundColor(OVERLAY_COLOR);
+            LayoutParams params =
+                    new LayoutParams(
+                            LayoutParams.MATCH_PARENT,
+                            LayoutParams.MATCH_PARENT,
+                            LayoutParams.TYPE_APPLICATION_OVERLAY,
+                            LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE,
+                            PixelFormat.TRANSLUCENT);
+            params.setTitle(name);
+            params.alpha = opacity;
+            mWindowManager.addView(view, params);
+            mSawViewsAdded.add(view);
+        });
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private void removeMySawOverlays() throws Throwable {
+        rule.runOnUiThread(() -> {
+            for (View view : mSawViewsAdded) {
+                mWindowManager.removeViewImmediate(view);
+            }
+            mSawViewsAdded.clear();
+        });
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private void waitForNoSawOverlays(String message) {
+        if (!mWmState.waitFor(message,
+                state -> mSawWindowsAdded.stream().allMatch(w -> !state.isWindowVisible(w)))) {
+            fail(message);
+        }
+        mSawWindowsAdded.clear();
+    }
+
+    private void removeOverlays() throws Throwable {
+        for (String app : APPS) {
+            stopPackage(app);
+        }
+        removeMySawOverlays();
+        waitForNoSawOverlays("SAWs not removed on time");
+        removeActivityOverlays();
+        waitForNoActivityOverlays("Activities not removed on time");
+        removeMyCustomToastOverlay();
+        waitForNoToastOverlays("Toasts not removed on time");
+    }
+
+    private void stopPackage(String packageName) {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mActivityManager.forceStopPackage(packageName));
+    }
+
+    private void ping(String packageName) {
+        mPongReceived = new ConditionVariable(false);
+        Intent intent = new Intent(Components.ActionReceiver.ACTION_PING);
+        intent.setComponent(repackage(packageName, Components.ActionReceiver.COMPONENT));
+        Bundle extras = new Bundle();
+        extras.putBinder(Components.ActionReceiver.EXTRA_CALLBACK, mPongCallback);
+        intent.putExtras(extras);
+        mContext.sendBroadcast(intent);
+        // Callback will be received on a binder thread, so we can block here
+        mPongReceived.block(PROCESS_RESPONSE_TIME_OUT_MS);
+    }
+
+    private int setBlockUntrustedTouchesMode(int mode) throws Exception {
+        return SystemUtil.callWithShellPermissionIdentity(() -> {
+            int previous = mInputManager.getBlockUntrustedTouchesMode(mContext);
+            mInputManager.setBlockUntrustedTouchesMode(mContext, mode);
+            return previous;
+        });
+    }
+
+    private float setMaximumObscuringOpacityForTouch(float opacity) throws Exception {
+        return SystemUtil.callWithShellPermissionIdentity(() -> {
+            float previous = mInputManager.getMaximumObscuringOpacityForTouch(mContext);
+            mInputManager.setMaximumObscuringOpacityForTouch(mContext, opacity);
+            return previous;
+        });
+    }
+
+    private ComponentName repackage(String packageName, ComponentName baseComponent) {
+        return new ComponentName(packageName, baseComponent.getClassName());
+    }
+
+    private class PongCallback extends Binder {
+        @Override
+        protected boolean onTransact(
+                int code, Parcel data, Parcel reply, int flags) throws RemoteException {
+            if (code == Components.ActionReceiver.CALLBACK_PONG) {
+                mPongReceived.open();
+                return true;
+            }
+            return super.onTransact(code, data, reply, flags);
+        }
+    }
+
+    public static class TestActivity extends Activity {
+        public View view;
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            view = new View(this);
+            view.setBackgroundColor(ACTIVITY_COLOR);
+            setContentView(view);
+        }
+    }
+
+    public static class OverlayActivity extends Activity {
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            View view = new View(this);
+            view.setBackgroundColor(OVERLAY_COLOR);
+            setContentView(view);
+            Window window = getWindow();
+            window.getAttributes().alpha = getIntent().getFloatExtra(
+                    Components.OverlayActivity.EXTRA_OPACITY, 1f);
+            window.addFlags(LayoutParams.FLAG_NOT_TOUCHABLE);
+            window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
index a259588..e1e247e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
@@ -36,7 +36,7 @@
      */
     public void cleanUp(List<ComponentName> activitiesInUsedInTest) throws Exception {
         launchHomeActivityNoWait();
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
 
         this.getWmState().waitForWithAmState(
                 state -> state.containsNoneOf(activitiesInUsedInTest),
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
index d3fa81d..3e80382 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
@@ -690,34 +690,9 @@
         moveTaskToPrimarySplitScreen(activity.getTaskId());
 
         final Class<? extends Activity> activityClass = activity.getClass();
-        waitAndAssertActivityEnterSplitScreenTransitions(activityClass, "enterSplitScreen");
-    }
-
-    /**
-     * Blocking call that will wait for activities to perform the entering split screen sequence of
-     * transitions.
-     * @see LifecycleTracker#waitForActivityTransitions(Class, List)
-     */
-    final void waitAndAssertActivityEnterSplitScreenTransitions(
-            Class<? extends Activity> activityClass, String message) {
-        log("Start waitAndAssertActivitySplitScreenTransitions");
-
-        final List<LifecycleLog.ActivityCallback> expectedTransitions =
-                LifecycleVerifier.getSplitScreenTransitionSequence(activityClass);
-
-        mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
-        if (!expectedTransitions.contains(ON_MULTI_WINDOW_MODE_CHANGED)) {
-            LifecycleVerifier.assertSequence(activityClass, getLifecycleLog(),
-                    expectedTransitions, message);
-        } else {
-            final List<LifecycleLog.ActivityCallback> extraSequence =
-                    Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST,
-                            ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE,
-                            ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED,
-                            ON_TOP_POSITION_LOST, ON_PAUSE);
-            LifecycleVerifier.assertSequenceMatchesOneOf(activityClass, getLifecycleLog(),
-                    Arrays.asList(expectedTransitions, extraSequence), message);
-        }
+        waitAndAssertActivityTransitions(activityClass,
+                LifecycleVerifier.getSplitScreenTransitionSequence(activityClass),
+                "enterSplitScreen");
     }
 
     final ActivityOptions getLaunchOptionsForFullscreen() {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
index 5409191..e33cbab 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
@@ -37,7 +37,6 @@
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Before;
@@ -51,7 +50,6 @@
  */
 @MediumTest
 @Presubmit
-@FlakyTest(bugId=137329632)
 @android.server.wm.annotation.Group3
 public class ActivityLifecycleFreeformTests extends ActivityLifecycleClientTestBase {
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
index e4dc868..b9e8b87 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
@@ -75,7 +75,7 @@
         mWmState.computeState(pipActivityName);
         final int stackId = mWmState.getStackIdByActivity(pipActivityName);
         assertNotEquals(stackId, INVALID_STACK_ID);
-        moveTopActivityToPinnedStack(stackId);
+        moveTopActivityToPinnedRootTask(stackId);
 
         // Wait and assert lifecycle
         waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
index 6cf6e99..30da1c4 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
@@ -295,14 +295,25 @@
     @Test
     public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
         // Launch a singleTop activity
-        final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
+        launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Wait for the activity to resume
         LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
 
         // Enter split screen
         getLifecycleLog().clear();
-        moveTaskToPrimarySplitScreenAndVerify(activity);
+        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Wait for the activity to relaunch and receive multi-window mode change
+        final List<LifecycleLog.ActivityCallback> expectedEnterSequence =
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
+                        ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED,
+                        ON_TOP_POSITION_LOST, ON_PAUSE);
+        waitForActivityTransitions(CallbackTrackingActivity.class, expectedEnterSequence);
+        LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, ON_CREATE,
+                        ON_RESUME), "moveToSplitScreen");
 
         // Exit split-screen
         getLifecycleLog().clear();
@@ -311,15 +322,10 @@
         // Wait for the activity to relaunch and receive multi-window mode change
         final List<LifecycleLog.ActivityCallback> expectedExitSequence =
                 Arrays.asList(ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
-                        ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED);
-
-        // ON_MULTI_WINDOW_MODE_CHANGED could happen before destroy
+                        ON_POST_CREATE, ON_RESUME, ON_PAUSE, ON_RESUME, ON_TOP_POSITION_GAINED);
         waitForActivityTransitions(CallbackTrackingActivity.class, expectedExitSequence);
         LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
-                expectedExitSequence, "moveFromSplitScreen");
-        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
-                LifecycleVerifier.transition(CallbackTrackingActivity.class,
-                        ON_MULTI_WINDOW_MODE_CHANGED),
+                Arrays.asList(ON_DESTROY, ON_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED),
                 "moveFromSplitScreen");
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
index f2cab44..c1bde74 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
@@ -56,7 +56,6 @@
 import android.content.pm.ActivityInfo;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
 import com.android.compatibility.common.util.AmUtils;
@@ -161,7 +160,7 @@
 
         // Move translucent activity into the stack with the first activity
         getLifecycleLog().clear();
-        moveActivityToStackOrOnTop(getComponentName(TranslucentActivity.class), firstActivityStack);
+        moveActivityToRootTaskOrOnTop(getComponentName(TranslucentActivity.class), firstActivityStack);
 
         // Wait for translucent activity to resume and first activity to pause
         waitAndAssertActivityStates(state(translucentActivity, ON_RESUME),
@@ -264,7 +263,6 @@
                 Arrays.asList(ON_RESUME), "secondDestroy");
     }
 
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishBottom() throws Exception {
         final Activity bottomActivity = launchActivityAndWait(FirstActivity.class);
@@ -284,13 +282,11 @@
                 "destroyOnBottom");
     }
 
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishAndLaunchOnResult() throws Exception {
         testLaunchForResultAndLaunchAfterResultSequence(EXTRA_LAUNCH_ON_RESULT);
     }
 
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishAndLaunchAfterOnResultInOnResume() throws Exception {
         testLaunchForResultAndLaunchAfterResultSequence(EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT);
@@ -576,7 +572,6 @@
     }
 
     @Test
-    @FlakyTest(bugId=127741025)
     public void testOnActivityResultAfterStop() throws Exception {
         final ActivityMonitor resultMonitor = getInstrumentation().addMonitor(
                 ResultActivity.class.getName(), null /* result */, false /* block */);
@@ -915,7 +910,6 @@
                 FirstActivity.class);
     }
 
-    @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
     @Test
     public void testFinishBelowDialogActivity() throws Exception {
         verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_PAUSE, "onPause",
@@ -940,7 +934,6 @@
         waitAndAssertActivityTransitions(activityClass, expectedSequence, "finish in " + stageName);
     }
 
-    @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
     @Test
     public void testFinishBelowTranslucentActivityAfterDelay() throws Exception {
         final Activity bottomActivity = launchActivityAndWait(CallbackTrackingActivity.class);
@@ -956,7 +949,6 @@
                 getLifecycleLog(), "finishBelow");
     }
 
-    @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
     @Test
     public void testFinishBelowFullscreenActivityAfterDelay() throws Exception {
         final Activity bottomActivity = launchActivityAndWait(CallbackTrackingActivity.class);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
index 33279f1..c7bac16 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
@@ -716,10 +716,10 @@
             getLifecycleLog().clear();
         }
 
-        // Lock screen removed, but nothing should change.
-        // Wait for something here, but don't expect anything to happen.
-        waitAndAssertActivityStates(state(showWhenLockedActivity, ON_DESTROY));
-        LifecycleVerifier.assertResumeToDestroySequence(
+        // When the lock screen is removed, the ShowWhenLocked activity will be dismissed using the
+        // back button, which should send it to the stopped state.
+        waitAndAssertActivityStates(state(showWhenLockedActivity, ON_STOP));
+        LifecycleVerifier.assertResumeToStopSequence(
                 ShowWhenLockedCallbackTrackingActivity.class, getLifecycleLog());
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
index afa03c2..f6d64bc 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
@@ -23,10 +23,11 @@
 import static android.content.Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
 import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.WindowManagerState.STATE_DESTROYED;
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
-import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.app.Components.ALIAS_TEST_ACTIVITY;
+import static android.server.wm.app.Components.NO_HISTORY_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -34,14 +35,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
-
 import android.server.wm.ActivityLauncher;
+import android.server.wm.app.Components;
 
 import org.junit.Test;
 
@@ -69,6 +71,8 @@
             = getComponentName(TestLaunchingActivity.class);
     private static final ComponentName LAUNCHING_AND_FINISH_ACTIVITY
             = getComponentName(LaunchingAndFinishActivity.class);
+    private static final ComponentName CLEAR_TASK_ON_LAUNCH_ACTIVITY
+            = getComponentName(ClearTaskOnLaunchActivity.class);
 
 
     /**
@@ -131,6 +135,51 @@
     }
 
     /**
+     * This test case tests show-when-locked behavior for a "no-history" activity.
+     * The no-history activity should be resumed over lockscreen.
+     */
+    @Test
+    public void testLaunchNoHistoryActivityShowWhenLocked() {
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.sleepDevice();
+
+        getLaunchActivityBuilder().setTargetActivity(NO_HISTORY_ACTIVITY)
+                .setIntentExtra(extra -> extra.putBoolean(
+                        Components.NoHistoryActivity.EXTRA_SHOW_WHEN_LOCKED, true))
+                .setUseInstrumentation().execute();
+        waitAndAssertActivityState(NO_HISTORY_ACTIVITY, STATE_RESUMED,
+            "Activity should be resumed");
+    }
+
+    /**
+     * This test case tests the behavior for a "no-history" activity after turning the screen off.
+     * The no-history activity must be resumed over lockscreen when launched again.
+     */
+    @Test
+    public void testNoHistoryActivityNotFinished() {
+        assumeTrue(supportsLockScreen());
+
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        // Launch a no-history activity
+        getLaunchActivityBuilder().setTargetActivity(NO_HISTORY_ACTIVITY)
+                .setIntentExtra(extra -> extra.putBoolean(
+                        Components.NoHistoryActivity.EXTRA_SHOW_WHEN_LOCKED, true))
+                .setUseInstrumentation().execute();
+
+        // Wait for the activity resumed.
+        mWmState.waitForActivityState(NO_HISTORY_ACTIVITY, STATE_RESUMED);
+
+        lockScreenSession.sleepDevice();
+
+        // Launch a no-history activity
+        launchActivity(NO_HISTORY_ACTIVITY);
+
+        // Wait for the activity resumed
+        waitAndAssertActivityState(NO_HISTORY_ACTIVITY, STATE_RESUMED,
+                "Activity must be resumed");
+    }
+
+    /**
      * This test case tests "single top" activity behavior.
      * - A first launched standard activity and a second launched single top
      * activity are in same task.
@@ -467,6 +516,49 @@
                 mWmState.getTaskByActivity(STANDARD_ACTIVITY).getTaskId());
     }
 
+    /**
+     * This test case tests behavior of activity launched with ClearTaskOnLaunch attribute and
+     * FLAG_ACTIVITY_RESET_TASK_IF_NEEDED. The activities above will be removed from the task when
+     * the clearTaskonlaunch activity is re-launched again.
+     */
+    @Test
+    public void testActivityWithClearTaskOnLaunch() {
+        // Launch a clearTaskonlaunch activity
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(CLEAR_TASK_ON_LAUNCH_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+                .execute();
+        mWmState.waitForActivityState(CLEAR_TASK_ON_LAUNCH_ACTIVITY, STATE_RESUMED);
+        final int taskId = mWmState.getTaskByActivity(CLEAR_TASK_ON_LAUNCH_ACTIVITY).getTaskId();
+
+        // Launch a standard activity
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(STANDARD_ACTIVITY)
+                .execute();
+        mWmState.waitForActivityState(STANDARD_ACTIVITY, STATE_RESUMED);
+
+        // Return to home
+        launchHomeActivity();
+
+        // Launch the clearTaskonlaunch activity again
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(CLEAR_TASK_ON_LAUNCH_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+                .execute();
+        mWmState.waitForActivityState(CLEAR_TASK_ON_LAUNCH_ACTIVITY, STATE_RESUMED);
+
+        // Make sure the task for the clearTaskonlaunch activity is front.
+        assertEquals("The task for the clearTaskonlaunch activity must be front.",
+                getActivityName(CLEAR_TASK_ON_LAUNCH_ACTIVITY),
+                mWmState.getTopActivityName(0));
+
+        assertEquals("Instance of the activity in its task must be cleared", 0,
+                mWmState.getActivityCountInTask(taskId, STANDARD_ACTIVITY));
+    }
+
     // Test activity
     public static class StandardActivity extends Activity {
     }
@@ -489,6 +581,10 @@
     }
 
     // Test activity
+    public static class ClearTaskOnLaunchActivity extends Activity {
+    }
+
+    // Test activity
     public static class SingleTopActivity extends Activity {
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
index e96e017..2359e9c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
@@ -36,7 +36,6 @@
 import android.app.Activity;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Test;
@@ -93,7 +92,6 @@
      * Verify that {@link Activity#finishAndRemoveTask()} removes all activities in task if called
      * for root of task.
      */
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishTask_FromRoot() throws Exception {
         final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
@@ -121,7 +119,6 @@
      * Verify that {@link Activity#finishAndRemoveTask()} removes all activities in task if called
      * for root of task. This version verifies lifecycle when top activity is translucent
      */
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishTask_FromRoot_TranslucentOnTop() throws Exception {
         final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
@@ -150,7 +147,6 @@
      * Verify that {@link Activity#finishAndRemoveTask()} only removes one activity in task if
      * called not for root of task.
      */
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishTask_NotFromRoot() throws Exception {
         final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
@@ -175,7 +171,6 @@
      * Verify the lifecycle of {@link Activity#finishAfterTransition()} for activity that has a
      * transition set.
      */
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishAfterTransition() throws Exception {
         final TransitionSourceActivity rootActivity =
@@ -211,7 +206,6 @@
      * Verify the lifecycle of {@link Activity#finishAfterTransition()} for activity with no
      * transition set.
      */
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishAfterTransition_noTransition() throws Exception {
         final Activity rootActivity = launchActivityAndWait(FirstActivity.class);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
index 94db5bc..233e260 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
@@ -36,6 +36,7 @@
 
 import android.app.Activity;
 import android.server.wm.lifecycle.ActivityLifecycleClientTestBase.CallbackTrackingActivity;
+import android.server.wm.lifecycle.ActivityLifecycleClientTestBase.ConfigChangeHandlingActivity;
 import android.server.wm.lifecycle.LifecycleLog.ActivityCallback;
 import android.util.Pair;
 
@@ -47,6 +48,7 @@
 class LifecycleVerifier {
 
     private static final Class CALLBACK_TRACKING_CLASS = CallbackTrackingActivity.class;
+    private static final Class CONFIG_CHANGE_HANDLING_CLASS = ConfigChangeHandlingActivity.class;
 
     static void assertLaunchSequence(Class<? extends Activity> activityClass,
             LifecycleLog lifecycleLog, LifecycleLog.ActivityCallback... expectedSubsequentEvents) {
@@ -299,9 +301,11 @@
     static List<LifecycleLog.ActivityCallback> getSplitScreenTransitionSequence(
             Class<? extends Activity> activityClass) {
         return CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass)
-                ? Arrays.asList(
+                ? CONFIG_CHANGE_HANDLING_CLASS.isAssignableFrom(activityClass)
+                ? Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST, ON_PAUSE)
+                : Arrays.asList(
                 ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
-                ON_CREATE, ON_MULTI_WINDOW_MODE_CHANGED, ON_START, ON_POST_CREATE, ON_RESUME,
+                ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
                 ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE)
                 : Arrays.asList(
                         ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index e281fdb..492e611 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -297,7 +297,8 @@
     }
 
     protected WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
-    TestTaskOrganizer mTaskOrganizer = new TestTaskOrganizer();
+    // Initialized in setUp to execute with proper permission, such as MANAGE_ACTIVITY_TASKS
+    TestTaskOrganizer mTaskOrganizer;
     // If the specific test should run using the task organizer or older API.
     // TODO(b/149338177): Fix all places setting this to fail to be able to use organizer API.
     public boolean mUseTaskOrganizer = true;
@@ -513,23 +514,29 @@
         pressWakeupButton();
         pressUnlockButton();
         launchHomeActivityNoWait();
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
 
-        // Clear launch params for all test packages to make sure each test is run in a clean state.
-        SystemUtil.runWithShellPermissionIdentity(
-                () -> mAtm.clearLaunchParamsForPackages(TEST_PACKAGES));
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            // TaskOrganizer ctor requires MANAGE_ACTIVITY_TASKS permission
+            mTaskOrganizer = new TestTaskOrganizer();
+            // Clear launch params for all test packages to make sure each test is run in a clean
+            // state.
+            mAtm.clearLaunchParamsForPackages(TEST_PACKAGES);
+        });
     }
 
     /** It always executes after {@link org.junit.After}. */
     private void tearDownBase() {
         mObjectTracker.tearDown(mPostAssertionRule::addError);
 
-        SystemUtil.runWithShellPermissionIdentity(
-                () -> mTaskOrganizer.unregisterOrganizerIfNeeded());
-        // Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but
-        // home are cleaned up from the stack at the end of each test. Am force stop shell commands
-        // might be asynchronous and could interrupt the stack cleanup process if executed first.
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        if (mTaskOrganizer != null) {
+            SystemUtil.runWithShellPermissionIdentity(mTaskOrganizer::unregisterOrganizerIfNeeded);
+        }
+        // Synchronous execution of removeRootTasksWithActivityTypes() ensures that all
+        // activities but home are cleaned up from the root task at the end of each test. Am force
+        // stop shell commands might be asynchronous and could interrupt the task cleanup
+        // process if executed first.
+        removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
         stopTestPackage(TEST_PACKAGE);
         stopTestPackage(SECOND_TEST_PACKAGE);
         stopTestPackage(THIRD_TEST_PACKAGE);
@@ -545,9 +552,9 @@
         SystemUtil.runWithShellPermissionIdentity(ActivityManager::resumeAppSwitches);
     }
 
-    protected void moveTopActivityToPinnedStack(int stackId) {
+    protected void moveTopActivityToPinnedRootTask(int rootTaskId) {
         SystemUtil.runWithShellPermissionIdentity(
-                () -> mAtm.moveTopActivityToPinnedStack(stackId, new Rect(0, 0, 500, 500))
+                () -> mAtm.moveTopActivityToPinnedRootTask(rootTaskId, new Rect(0, 0, 500, 500))
         );
     }
 
@@ -693,15 +700,15 @@
         getInstrumentation().getUiAutomation().injectInputEvent(upEvent, sync);
     }
 
-    protected void removeStacksWithActivityTypes(int... activityTypes) {
+    protected void removeRootTasksWithActivityTypes(int... activityTypes) {
         SystemUtil.runWithShellPermissionIdentity(
-                () -> mAtm.removeStacksWithActivityTypes(activityTypes));
+                () -> mAtm.removeRootTasksWithActivityTypes(activityTypes));
         waitForIdle();
     }
 
-    protected void removeStacksInWindowingModes(int... windowingModes) {
+    protected void removeRootTasksInWindowingModes(int... windowingModes) {
         SystemUtil.runWithShellPermissionIdentity(
-                () -> mAtm.removeStacksInWindowingModes(windowingModes)
+                () -> mAtm.removeRootTasksInWindowingModes(windowingModes)
         );
         waitForIdle();
     }
@@ -939,10 +946,13 @@
         return result[0];
     }
 
-    /** Move activity to stack or on top of the given stack when the stack is a leak task. */
-    protected void moveActivityToStackOrOnTop(ComponentName activityName, int stackId) {
+    /**
+     * Move activity to root task or on top of the given root task when the root task is also a leaf
+     * task.
+     */
+    protected void moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId) {
         mWmState.computeState(activityName);
-        WindowManagerState.ActivityTask rootTask = getRootTask(stackId);
+        WindowManagerState.ActivityTask rootTask = getRootTask(rootTaskId);
         if (rootTask.getActivities().size() != 0) {
             // If the root task is a 1-level task, start the activity on top of given task.
             getLaunchActivityBuilder()
@@ -956,10 +966,10 @@
         } else {
             final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
             SystemUtil.runWithShellPermissionIdentity(
-                    () -> mAtm.moveTaskToStack(taskId, stackId, true));
+                    () -> mAtm.moveTaskToRootTask(taskId, rootTaskId, true));
         }
         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
-                .setStackId(stackId)
+                .setStackId(rootTaskId)
                 .build());
     }
 
@@ -971,10 +981,10 @@
                 () -> mAtm.resizeTask(taskId, new Rect(left, top, right, bottom)));
     }
 
-    protected void resizeDockedStack(
-            int stackWidth, int stackHeight, int taskWidth, int taskHeight) {
+    protected void resizePrimarySplitScreen(
+            int rootTaskWidth, int rootTaskHeight, int taskWidth, int taskHeight) {
         SystemUtil.runWithShellPermissionIdentity(() ->
-                mAtm.resizeDockedStack(new Rect(0, 0, stackWidth, stackHeight),
+                mAtm.resizePrimarySplitScreen(new Rect(0, 0, rootTaskWidth, rootTaskHeight),
                         new Rect(0, 0, taskWidth, taskHeight)));
     }
 
@@ -984,16 +994,6 @@
         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
     }
 
-    // Utility method for debugging, not used directly here, but useful, so kept around.
-    protected void printStacksAndTasks() {
-        SystemUtil.runWithShellPermissionIdentity(() -> {
-            final String output = mAtm.listAllStacks();
-            for (String line : output.split("\\n")) {
-                log(line);
-            }
-        });
-    }
-
     protected boolean supportsVrMode() {
         return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE);
     }
@@ -1062,16 +1062,17 @@
         mWmState.waitForWithAmState(state -> activityClassName.equals(state.getFocusedActivity()),
                 "activity to be on top");
 
-        mWmState.assertSanity();
+        mWmState.assertValidity();
         mWmState.assertFocusedActivity(message, activityName);
         assertTrue("Activity must be resumed",
                 mWmState.hasActivityState(activityName, STATE_RESUMED));
-        final int frontStackId = mWmState.getFrontRootTaskId(displayId);
-        WindowManagerState.ActivityTask frontStackOnDisplay =
-                mWmState.getRootTask(frontStackId);
-        assertEquals("Resumed activity of front stack of the target display must match. " + message,
-                activityClassName, frontStackOnDisplay.mResumedActivity);
-        mWmState.assertFocusedStack("Top activity's stack must also be on top", frontStackId);
+        final int frontRootTaskId = mWmState.getFrontRootTaskId(displayId);
+        WindowManagerState.ActivityTask frontRootTaskOnDisplay =
+                mWmState.getRootTask(frontRootTaskId);
+        assertEquals(
+                "Resumed activity of front root task of the target display must match. " + message,
+                activityClassName, frontRootTaskOnDisplay.mResumedActivity);
+        mWmState.assertFocusedStack("Top activity's rootTask must also be on top", frontRootTaskId);
         mWmState.assertVisibility(activityName, true /* visible */);
     }
 
@@ -1103,8 +1104,18 @@
         return uiModeLockedToVrHeadset;
     }
 
+    /** Returns true if the default display supports split screen multi-window. */
     protected boolean supportsSplitScreenMultiWindow() {
-        return ActivityTaskManager.supportsSplitScreenMultiWindow(mContext);
+        Display defaultDisplay = mDm.getDisplay(DEFAULT_DISPLAY);
+        return supportsSplitScreenMultiWindow(mContext.createDisplayContext(defaultDisplay));
+    }
+
+    /**
+     * Returns true if the display associated with the supplied {@code context} supports split
+     * screen multi-window.
+     */
+    protected boolean supportsSplitScreenMultiWindow(Context context) {
+        return ActivityTaskManager.supportsSplitScreenMultiWindow(context);
     }
 
     protected boolean hasHomeScreen() {
@@ -1430,7 +1441,7 @@
         @Override
         public void close() {
             if (mRemoveActivitiesOnClose) {
-                removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+                removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
             }
 
             setLockDisabled(mIsLockDisabled);
@@ -1441,9 +1452,9 @@
             // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for
             // the stale credential.
             // TODO (b/112015010) If keyguard is occluded, credential cannot be removed as expected.
-            // LockScreenSession#close is always calls before stop all test activities,
-            // which could cause keyguard stay at occluded after wakeup.
-            // If Keyguard is occluded, press back key can close ShowWhenLocked activity.
+            // LockScreenSession#close is always called before stopping all test activities,
+            // which could cause the keyguard to stay occluded after wakeup.
+            // If Keyguard is occluded, pressing the back key can hide the ShowWhenLocked activity.
             pressBackButton();
 
             // If device is unlocked, there might have ShowWhenLocked activity runs on,
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ProtoExtractors.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ProtoExtractors.java
index ff47a7e..1f15ea3 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ProtoExtractors.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ProtoExtractors.java
@@ -53,6 +53,8 @@
             return config;
         }
         config.setAppBounds(extract(proto.appBounds));
+        config.setBounds(extract(proto.bounds));
+        config.setMaxBounds(extract(proto.maxBounds));
         config.setWindowingMode(proto.windowingMode);
         config.setActivityType(proto.activityType);
         return config;
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
index ca33b90..dc70a63 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
@@ -44,8 +44,7 @@
     private void registerOrganizerIfNeeded() {
         if (mRegistered) return;
 
-        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        registerOrganizer();
         mRegistered = true;
     }
 
@@ -67,7 +66,7 @@
 
     void dismissedSplitScreen() {
         // Re-set default launch root.
-        TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
+        setLaunchRoot(Display.DEFAULT_DISPLAY, null);
 
         // Re-parent everything back to the display from the splits so that things are as they were.
         final List<ActivityManager.RunningTaskInfo> children = new ArrayList<>();
@@ -160,11 +159,21 @@
                 && (mRootPrimary == null || mRootPrimary.taskId == taskInfo.taskId)) {
             mRootPrimary = taskInfo;
             processRootPrimaryTaskInfoChanged();
+            addChildTasks(taskInfo);
         }
 
         if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                 && (mRootSecondary == null || mRootSecondary.taskId == taskInfo.taskId)) {
             mRootSecondary = taskInfo;
+            addChildTasks(taskInfo);
+        }
+    }
+
+    private void addChildTasks(ActivityManager.RunningTaskInfo taskInfo) {
+        List<ActivityManager.RunningTaskInfo> children =
+                getChildTasks(taskInfo.getToken(), null /* activityTypes */);
+        for (ActivityManager.RunningTaskInfo child : children) {
+            mKnownTasks.put(child.taskId, child);
         }
     }
 
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index 23c15b4..dd8ac68 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -17,14 +17,15 @@
 package android.server.wm;
 
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.ProtoExtractors.extract;
 import static android.server.wm.StateLogger.log;
@@ -34,9 +35,12 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.app.ActivityTaskManager;
 import android.content.ComponentName;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -50,14 +54,14 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.server.wm.nano.ActivityRecordProto;
 import com.android.server.wm.nano.AppTransitionProto;
+import com.android.server.wm.nano.ConfigurationContainerProto;
 import com.android.server.wm.nano.DisplayAreaProto;
+import com.android.server.wm.nano.DisplayContentProto;
 import com.android.server.wm.nano.DisplayFramesProto;
 import com.android.server.wm.nano.IdentifierProto;
 import com.android.server.wm.nano.KeyguardControllerProto;
-import com.android.server.wm.nano.ActivityRecordProto;
-import com.android.server.wm.nano.ConfigurationContainerProto;
-import com.android.server.wm.nano.DisplayContentProto;
 import com.android.server.wm.nano.PinnedStackControllerProto;
 import com.android.server.wm.nano.RootWindowContainerProto;
 import com.android.server.wm.nano.TaskProto;
@@ -65,10 +69,10 @@
 import com.android.server.wm.nano.WindowContainerProto;
 import com.android.server.wm.nano.WindowFramesProto;
 import com.android.server.wm.nano.WindowManagerServiceDumpProto;
-import com.android.server.wm.nano.WindowTokenProto;
 import com.android.server.wm.nano.WindowStateAnimatorProto;
 import com.android.server.wm.nano.WindowStateProto;
 import com.android.server.wm.nano.WindowSurfaceControllerProto;
+import com.android.server.wm.nano.WindowTokenProto;
 
 import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
 
@@ -128,7 +132,7 @@
     // Stacks in z-order with the top most at the front of the list, starting with primary display.
     private final List<ActivityTask> mRootTasks = new ArrayList<>();
     // Windows in z-order with the top most at the front of the list.
-    private List<WindowState> mWindowStates = new ArrayList();
+    private final List<WindowState> mWindowStates = new ArrayList<>();
     private KeyguardControllerState mKeyguardControllerState;
     private final List<String> mPendingActivities = new ArrayList<>();
     private int mTopFocusedTaskId = -1;
@@ -242,12 +246,7 @@
         return TYPE_NAVIGATION_BAR == navState.getType();
     }
 
-    /** Enable/disable the mFocusedWindow check during the computeState.*/
-    void setSanityCheckWithFocusedWindow(boolean sanityCheckFocusedWindow) {
-        mSanityCheckFocusedWindow = sanityCheckFocusedWindow;
-    }
-
-    /**
+/**
      * For a given WindowContainer, traverse down the hierarchy and add all children of type
      * {@code T} to {@code outChildren}.
      */
@@ -288,6 +287,11 @@
         }
     }
 
+    /** Enable/disable the mFocusedWindow check during the computeState.*/
+    void setSanityCheckWithFocusedWindow(boolean sanityCheckFocusedWindow) {
+        mSanityCheckFocusedWindow = sanityCheckFocusedWindow;
+    }
+
     public void computeState() {
         // It is possible the system is in the middle of transition to the right state when we get
         // the dump. We try a few times to get the information we need before giving up.
@@ -467,15 +471,30 @@
         return null;
     }
 
+    @Nullable
+    DisplayArea getTaskDisplayArea(ComponentName activityName) {
+        final List<DisplayArea> result = new ArrayList<>();
+        for (DisplayContent display : mDisplays) {
+            final DisplayArea tda = display.getTaskDisplayArea(activityName);
+            if (tda != null) {
+                result.add(tda);
+            }
+        }
+        assertWithMessage("There must be exactly one activity among all TaskDisplayAreas.")
+                .that(result.size()).isAtMost(1);
+
+        return result.stream().findFirst().orElse(null);
+    }
+
     int getFrontRootTaskId(int displayId) {
         return getDisplay(displayId).mRootTasks.get(0).mRootTaskId;
     }
 
-    int getFrontStackActivityType(int displayId) {
+    public int getFrontStackActivityType(int displayId) {
         return getDisplay(displayId).mRootTasks.get(0).getActivityType();
     }
 
-    int getFrontStackWindowingMode(int displayId) {
+    public int getFrontStackWindowingMode(int displayId) {
         return getDisplay(displayId).mRootTasks.get(0).getWindowingMode();
     }
 
@@ -494,25 +513,25 @@
         return mTopFocusedTaskId;
     }
 
-    int getFocusedStackActivityType() {
+    public int getFocusedStackActivityType() {
         final ActivityTask stack = getRootTask(mTopFocusedTaskId);
         return stack != null ? stack.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
     }
 
-    int getFocusedStackWindowingMode() {
+    public int getFocusedStackWindowingMode() {
         final ActivityTask stack = getRootTask(mTopFocusedTaskId);
         return stack != null ? stack.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
     }
 
-    String getFocusedActivity() {
+    public String getFocusedActivity() {
         return mTopResumedActivityRecord;
     }
 
-    int getResumedActivitiesCount() {
+    public int getResumedActivitiesCount() {
         return mResumedActivitiesInStacks.size();
     }
 
-    int getResumedActivitiesCountInPackage(String packageName) {
+    public int getResumedActivitiesCountInPackage(String packageName) {
         final String componentPrefix = packageName + "/";
         int count = 0;
         for (int i = mDisplays.size() - 1; i >= 0; --i) {
@@ -527,7 +546,7 @@
         return count;
     }
 
-    String getResumedActivityOnDisplay(int displayId) {
+    public String getResumedActivityOnDisplay(int displayId) {
         return getDisplay(displayId).mResumedActivity;
     }
 
@@ -535,11 +554,11 @@
         return mKeyguardControllerState;
     }
 
-    boolean containsStack(int windowingMode, int activityType) {
+    public boolean containsStack(int windowingMode, int activityType) {
         return countStacks(windowingMode, activityType) > 0;
     }
 
-    int countStacks(int windowingMode, int activityType) {
+    public int countStacks(int windowingMode, int activityType) {
         int count = 0;
         for (ActivityTask stack : mRootTasks) {
             if (activityType != ACTIVITY_TYPE_UNDEFINED
@@ -555,7 +574,7 @@
         return count;
     }
 
-    ActivityTask getRootTask(int taskId) {
+    public ActivityTask getRootTask(int taskId) {
         for (ActivityTask stack : mRootTasks) {
             if (taskId == stack.mRootTaskId) {
                 return stack;
@@ -564,7 +583,7 @@
         return null;
     }
 
-    ActivityTask getStackByActivityType(int activityType) {
+    public ActivityTask getStackByActivityType(int activityType) {
         for (ActivityTask stack : mRootTasks) {
             if (activityType == stack.getActivityType()) {
                 return stack;
@@ -573,7 +592,7 @@
         return null;
     }
 
-    ActivityTask getStandardStackByWindowingMode(int windowingMode) {
+    public ActivityTask getStandardStackByWindowingMode(int windowingMode) {
         for (ActivityTask stack : mRootTasks) {
             if (stack.getActivityType() != ACTIVITY_TYPE_STANDARD) {
                 continue;
@@ -622,7 +641,7 @@
     }
 
     /** Get display id by activity on it. */
-    int getDisplayByActivity(ComponentName activityComponent) {
+    public int getDisplayByActivity(ComponentName activityComponent) {
         final ActivityTask task = getTaskByActivity(activityComponent);
         if (task == null) {
             return -1;
@@ -638,11 +657,11 @@
         return new ArrayList<>(mRootTasks);
     }
 
-    int getStackCount() {
+    public int getStackCount() {
         return mRootTasks.size();
     }
 
-    int getDisplayCount() {
+    public int getDisplayCount() {
         return mDisplays.size();
     }
 
@@ -662,7 +681,7 @@
         return true;
     }
 
-    boolean containsActivityInWindowingMode(ComponentName activityName, int windowingMode) {
+    public boolean containsActivityInWindowingMode(ComponentName activityName, int windowingMode) {
         for (ActivityTask stack : mRootTasks) {
             final Activity activity = stack.getActivity(activityName);
             if (activity != null && activity.getWindowingMode() == windowingMode) {
@@ -672,7 +691,7 @@
         return false;
     }
 
-    boolean isActivityVisible(ComponentName activityName) {
+    public boolean isActivityVisible(ComponentName activityName) {
         for (ActivityTask stack : mRootTasks) {
             final Activity activity = stack.getActivity(activityName);
             if (activity != null) return activity.visible;
@@ -680,7 +699,7 @@
         return false;
     }
 
-    boolean isActivityTranslucent(ComponentName activityName) {
+    public boolean isActivityTranslucent(ComponentName activityName) {
         for (ActivityTask stack : mRootTasks) {
             final Activity activity = stack.getActivity(activityName);
             if (activity != null) return activity.translucent;
@@ -688,7 +707,7 @@
         return false;
     }
 
-    boolean isBehindOpaqueActivities(ComponentName activityName) {
+    public boolean isBehindOpaqueActivities(ComponentName activityName) {
         final String fullName = getActivityName(activityName);
         for (ActivityTask stack : mRootTasks) {
             final Activity activity =
@@ -706,7 +725,7 @@
         return false;
     }
 
-    boolean containsStartedActivities() {
+    public boolean containsStartedActivities() {
         for (ActivityTask stack : mRootTasks) {
             final Activity activity = stack.getActivity(
                     (a) -> !a.state.equals(STATE_STOPPED) && !a.state.equals(STATE_DESTROYED));
@@ -744,6 +763,14 @@
         return ComponentName.unflattenFromString(activity.name);
     }
 
+    ActivityTask getDreamTask() {
+        final ActivityTask dreamStack = getStackByActivityType(ACTIVITY_TYPE_DREAM);
+        if (dreamStack != null) {
+            return dreamStack.getTopTask();
+        }
+        return null;
+    }
+
     ActivityTask getHomeTask() {
         final ActivityTask homeStack = getStackByActivityType(ACTIVITY_TYPE_HOME);
         if (homeStack != null) {
@@ -780,7 +807,7 @@
         return getTaskByActivity(activityName, WINDOWING_MODE_UNDEFINED);
     }
 
-    ActivityTask getTaskByActivity(ComponentName activityName, int windowingMode) {
+    public ActivityTask getTaskByActivity(ComponentName activityName, int windowingMode) {
         for (ActivityTask stack : mRootTasks) {
             if (windowingMode == WINDOWING_MODE_UNDEFINED
                     || windowingMode == stack.getWindowingMode()) {
@@ -919,7 +946,7 @@
     }
 
     /** Check if at least one window which matches the specified name has shown it's surface. */
-    boolean isWindowSurfaceShown(String windowName) {
+    public boolean isWindowSurfaceShown(String windowName) {
         for (WindowState window : mWindowStates) {
             if (window.getName().equals(windowName)) {
                 if (window.isSurfaceShown()) {
@@ -931,7 +958,7 @@
     }
 
     /** Check if at least one window which matches provided window name is visible. */
-    boolean isWindowVisible(String windowName) {
+    public boolean isWindowVisible(String windowName) {
         for (WindowState window : mWindowStates) {
             if (window.getName().equals(windowName)) {
                 if (window.isVisible()) {
@@ -942,7 +969,7 @@
         return false;
     }
 
-    boolean allWindowSurfacesShown(String windowName) {
+    public boolean allWindowSurfacesShown(String windowName) {
         boolean allShown = false;
         for (WindowState window : mWindowStates) {
             if (window.getName().equals(windowName)) {
@@ -992,7 +1019,7 @@
         return null;
     }
 
-    Rect getStableBounds() {
+    public Rect getStableBounds() {
         return getDisplay(DEFAULT_DISPLAY).mStableBounds;
     }
 
@@ -1008,11 +1035,11 @@
         return mRotation;
     }
 
-    int getLastOrientation() {
+    public int getLastOrientation() {
         return mLastOrientation;
     }
 
-    int getFocusedDisplayId() {
+    public int getFocusedDisplayId() {
         return mFocusedDisplayId;
     }
 
@@ -1037,7 +1064,7 @@
         private String mAppTransitionState;
 
         DisplayContent(DisplayContentProto proto) {
-            super(proto.windowContainer);
+            super(proto.rootDisplayArea.windowContainer);
             mId = proto.id;
             mFocusedRootTaskId = proto.focusedRootTaskId;
             mSingleTaskInstance = proto.singleTaskInstance;
@@ -1079,6 +1106,10 @@
 
         }
 
+        public String getName() {
+            return mName;
+        }
+
         private void addRootTasks() {
             // TODO(b/149338177): figure out how CTS tests deal with organizer. For now,
             //                    don't treat them as regular stacks
@@ -1109,6 +1140,21 @@
             return false;
         }
 
+        @Nullable
+        DisplayArea getTaskDisplayArea(ComponentName activityName) {
+            List<DisplayArea> taskDisplayAreas = new ArrayList<>();
+            collectDescendantsOfTypeIf(DisplayArea.class, DisplayArea::isTaskDisplayArea, this,
+                    taskDisplayAreas);
+            List<DisplayArea> result = taskDisplayAreas.stream().filter(
+                    tda -> tda.containsActivity(activityName))
+                    .collect(Collectors.toList());
+
+            assertWithMessage("There must be exactly one activity among all TaskDisplayAreas.")
+                    .that(result.size()).isAtMost(1);
+
+            return result.stream().findFirst().orElse(null);
+        }
+
         ArrayList<ActivityTask> getRootTasks() {
             return mRootTasks;
         }
@@ -1125,10 +1171,6 @@
             return mStableBounds;
         }
 
-        String getName() {
-            return mName;
-        }
-
         int getFlags() {
             return mFlags;
         }
@@ -1272,7 +1314,7 @@
             return null;
         }
 
-        Activity getActivity(ComponentName activityName) {
+        public Activity getActivity(ComponentName activityName) {
             final String fullName = getActivityName(activityName);
             return getActivity((activity) -> activity.name.equals(fullName));
         }
@@ -1397,7 +1439,7 @@
             return windowingMode == requestedWindowingMode;
         }
 
-        int getWindowingMode() {
+        public int getWindowingMode() {
             if (mFullConfiguration == null) {
                 return WINDOWING_MODE_UNDEFINED;
             }
@@ -1418,8 +1460,34 @@
         }
     }
     public static class DisplayArea extends WindowContainer {
+        final boolean mIsTaskDisplayArea;
+        ArrayList<Activity> mActivities;
+
         DisplayArea(DisplayAreaProto proto) {
             super(proto.windowContainer);
+            mIsTaskDisplayArea = proto.isTaskDisplayArea;
+            if (mIsTaskDisplayArea) {
+                mActivities = new ArrayList<>();
+                collectDescendantsOfType(Activity.class, this, mActivities);
+            }
+        }
+
+        boolean isTaskDisplayArea() {
+            return mIsTaskDisplayArea;
+        }
+
+        boolean containsActivity(ComponentName activityName) {
+            if (!mIsTaskDisplayArea) {
+                return false;
+            }
+
+            final String fullName = getActivityName(activityName);
+            for (Activity a : mActivities) {
+                if (a.name.equals(fullName)) {
+                    return true;
+                }
+            }
+            return false;
         }
     }
     public static class WindowToken extends WindowContainer {
@@ -1473,6 +1541,8 @@
 
     static abstract class WindowContainer extends ConfigurationContainer {
 
+        protected String mName;
+        protected final String mAppToken;
         protected boolean mFullscreen;
         protected Rect mBounds;
         protected int mOrientation;
@@ -1482,6 +1552,9 @@
 
         WindowContainer(WindowContainerProto proto) {
             super(proto.configurationContainer);
+            IdentifierProto identifierProto = proto.identifier;
+            mName = identifierProto.title;
+            mAppToken = Integer.toHexString(identifierProto.hashCode);
             mOrientation = proto.orientation;
             for (int i = 0; i < proto.children.length; i++) {
                 final WindowContainer child = getWindowContainer(proto.children[i], this);
@@ -1492,6 +1565,15 @@
             mVisible = proto.visible;
         }
 
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        String getToken() {
+            return mAppToken;
+        }
+
         Rect getBounds() {
             return mBounds;
         }
@@ -1516,8 +1598,6 @@
         private static final int WINDOW_TYPE_EXITING = 2;
         private static final int WINDOW_TYPE_DEBUGGER = 3;
 
-        private String mName;
-        private final String mAppToken;
         private final int mWindowType;
         private int mType = 0;
         private int mDisplayId;
@@ -1535,9 +1615,6 @@
 
         WindowState(WindowStateProto proto) {
             super(proto.windowContainer);
-            IdentifierProto identifierProto = proto.identifier;
-            mName = identifierProto.title;
-            mAppToken = Integer.toHexString(identifierProto.hashCode);
             mDisplayId = proto.displayId;
             mStackId = proto.stackId;
             if (proto.attributes != null) {
@@ -1577,15 +1654,6 @@
             collectDescendantsOfType(WindowState.class, this, mSubWindows);
         }
 
-        @NonNull
-        public String getName() {
-            return mName;
-        }
-
-        String getToken() {
-            return mAppToken;
-        }
-
         boolean isStartingWindow() {
             return mWindowType == WINDOW_TYPE_STARTING;
         }
@@ -1675,4 +1743,9 @@
     int defaultMinimalTaskSize(int displayId) {
         return dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP, getDisplay(displayId).getDpi());
     }
+
+    int defaultMinimalDisplaySizeForSplitScreen(int displayId) {
+        return dpToPx(ActivityTaskManager.DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP,
+                getDisplay(displayId).getDpi());
+    }
 }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
index 1a77ba4..ad50716 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
@@ -53,7 +53,7 @@
 public class WindowManagerStateHelper extends WindowManagerState {
 
     /**
-     * Compute AM and WM state of device, check sanity and bounds.
+     * Compute AM and WM state of device, check validity and bounds.
      * WM state will include only visible windows, stack and task bounds will be compared.
      *
      * @param componentNames array of activity names to wait for.
@@ -65,7 +65,7 @@
     }
 
     /**
-     * Compute AM and WM state of device, check sanity and bounds.
+     * Compute AM and WM state of device, check validity and bounds.
      * WM state will include only visible windows, stack and task bounds will be compared.
      *
      * @param waitForActivitiesVisible array of activity names to wait for.
@@ -90,12 +90,12 @@
      * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
      * @param waitForActivitiesVisible  array of activity states to wait for.
      */
-    void waitForValidState(WaitForValidActivityState... waitForActivitiesVisible) {
+    public void waitForValidState(WaitForValidActivityState... waitForActivitiesVisible) {
         if (!Condition.waitFor("valid stacks and activities states", () -> {
             // TODO: Get state of AM and WM at the same time to avoid mismatches caused by
             // requesting dump in some intermediate state.
             computeState();
-            return !(shouldWaitForSanityCheck()
+            return !(shouldWaitForValidityCheck()
                     || shouldWaitForValidStacks()
                     || shouldWaitForActivities(waitForActivitiesVisible)
                     || shouldWaitForWindows());
@@ -104,7 +104,7 @@
         }
     }
 
-    void waitForAllStoppedActivities() {
+    public void waitForAllStoppedActivities() {
         if (!Condition.waitFor("all started activities have been removed", () -> {
             computeState();
             return !containsStartedActivities();
@@ -121,7 +121,7 @@
      * waiting-for-debugger window, but real activity window won't show up since we're waiting
      * for debugger.
      */
-    void waitForDebuggerWindowVisible(ComponentName activityName) {
+    public void waitForDebuggerWindowVisible(ComponentName activityName) {
         Condition.waitFor("debugger window", () -> {
             computeState();
             return !shouldWaitForDebuggerWindow(activityName)
@@ -129,7 +129,7 @@
         });
     }
 
-    void waitForHomeActivityVisible() {
+    public void waitForHomeActivityVisible() {
         ComponentName homeActivity = getHomeActivityName();
         // Sometimes this function is called before we know what Home Activity is
         if (homeActivity == null) {
@@ -141,7 +141,7 @@
         waitForValidState(homeActivity);
     }
 
-    void waitForRecentsActivityVisible() {
+    public void waitForRecentsActivityVisible() {
         if (isHomeRecentsComponent()) {
             waitForHomeActivityVisible();
         } else {
@@ -150,28 +150,33 @@
         }
     }
 
-    void waitForKeyguardShowingAndNotOccluded() {
+    public void waitForDreamGone() {
+        assertTrue("Dream must be gone",
+                waitForWithAmState(state -> state.getDreamTask() == null, "DreamActivity gone"));
+    }
+
+    public void waitForKeyguardShowingAndNotOccluded() {
         waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
                         && !state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY),
                 "Keyguard showing");
     }
 
-    void waitForKeyguardShowingAndOccluded() {
+    public void waitForKeyguardShowingAndOccluded() {
         waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
                         && state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY),
                 "Keyguard showing and occluded");
     }
 
-    void waitForAodShowing() {
+    public void waitForAodShowing() {
         waitForWithAmState(state -> state.getKeyguardControllerState().aodShowing, "AOD showing");
     }
 
-    void waitForKeyguardGone() {
+    public void waitForKeyguardGone() {
         waitForWithAmState(state -> !state.getKeyguardControllerState().keyguardShowing,
                 "Keyguard gone");
     }
 
-    void waitAndAssertKeyguardGone() {
+    public void waitAndAssertKeyguardGone() {
         assertTrue("Keyguard must be gone",
                 waitForWithAmState(
                         state -> !state.getKeyguardControllerState().keyguardShowing,
@@ -179,7 +184,7 @@
     }
 
     /** Wait for specific rotation for the default display. Values are Surface#Rotation */
-    void waitForRotation(int rotation) {
+    public void waitForRotation(int rotation) {
         waitForWithAmState(state -> state.getRotation() == rotation, "Rotation: " + rotation);
     }
 
@@ -187,12 +192,12 @@
      * Wait for specific orientation for the default display.
      * Values are ActivityInfo.ScreenOrientation
      */
-    void waitForLastOrientation(int orientation) {
+    public void waitForLastOrientation(int orientation) {
         waitForWithAmState(state -> state.getLastOrientation() == orientation,
                 "LastOrientation: " + orientation);
     }
 
-    void waitAndAssertLastOrientation(String message, int screenOrientation) {
+    public void waitAndAssertLastOrientation(String message, int screenOrientation) {
         if (screenOrientation != getLastOrientation()) {
             waitForLastOrientation(screenOrientation);
         }
@@ -202,7 +207,7 @@
     /**
      * Wait for orientation for the Activity
      */
-    void waitForActivityOrientation(ComponentName activityName, int orientation) {
+    public void waitForActivityOrientation(ComponentName activityName, int orientation) {
         waitForWithAmState(amState -> {
             final ActivityTask task = amState.getTaskByActivity(activityName);
             if (task == null) {
@@ -212,7 +217,7 @@
         }, "orientation of " + getActivityName(activityName) + " to be " + orientation);
     }
 
-    void waitForDisplayUnfrozen() {
+    public void waitForDisplayUnfrozen() {
         waitForWithAmState(state -> !state.isDisplayFrozen(), "Display unfrozen");
     }
 
@@ -227,12 +232,12 @@
                 getActivityName(activityName) + " to be removed");
     }
 
-    void waitAndAssertActivityRemoved(ComponentName activityName) {
+    public void waitAndAssertActivityRemoved(ComponentName activityName) {
         waitForActivityRemoved(activityName);
         assertNotExist(activityName);
     }
 
-    void waitForFocusedStack(int windowingMode, int activityType) {
+    public void waitForFocusedStack(int windowingMode, int activityType) {
         waitForWithAmState(state ->
                         (activityType == ACTIVITY_TYPE_UNDEFINED
                                 || state.getFocusedStackActivityType() == activityType)
@@ -241,32 +246,32 @@
                 "focused stack");
     }
 
-    void waitForPendingActivityContain(ComponentName activity) {
+    public void waitForPendingActivityContain(ComponentName activity) {
         waitForWithAmState(state -> state.pendingActivityContain(activity),
                 getActivityName(activity) + " in pending list");
     }
 
-    void waitForAppTransitionIdleOnDisplay(int displayId) {
+    public void waitForAppTransitionIdleOnDisplay(int displayId) {
         waitForWithAmState(
                 state -> WindowManagerState.APP_STATE_IDLE.equals(
                         state.getDisplay(displayId).getAppTransitionState()),
                 "app transition idle on Display " + displayId);
     }
 
-    void waitAndAssertNavBarShownOnDisplay(int displayId) {
+    public void waitAndAssertNavBarShownOnDisplay(int displayId) {
         assertTrue(waitForWithAmState(
                 state -> state.getAndAssertSingleNavBarWindowOnDisplay(displayId) != null,
                 "navigation bar #" + displayId + " show"));
     }
 
-    void waitAndAssertKeyguardShownOnSecondaryDisplay(int displayId) {
+    public void waitAndAssertKeyguardShownOnSecondaryDisplay(int displayId) {
         assertTrue("KeyguardDialog must be shown on secondary display " + displayId,
                 waitForWithAmState(
                         state -> isKeyguardOnSecondaryDisplay(state, displayId),
                         "keyguard window to show"));
     }
 
-    void waitAndAssertKeyguardGoneOnSecondaryDisplay(int displayId) {
+    public void waitAndAssertKeyguardGoneOnSecondaryDisplay(int displayId) {
         assertTrue("KeyguardDialog must be gone on secondary display " + displayId,
                 waitForWithAmState(
                         state -> !isKeyguardOnSecondaryDisplay(state, displayId),
@@ -279,6 +284,11 @@
         }, windowName + "'s surface is disappeared");
     }
 
+    /** A variant of waitForWithAmState with different parameter order for better Kotlin interop. */
+    public boolean waitForWithAmState(String message, Predicate<WindowManagerState> waitCondition) {
+        return waitForWithAmState(waitCondition, message);
+    }
+
     public boolean waitForWithAmState(Predicate<WindowManagerState> waitCondition,
             String message) {
         return waitFor((amState) -> waitCondition.test(amState), message);
@@ -292,8 +302,13 @@
         }, message);
     }
 
+    /** A variant of waitFor with different parameter order for better Kotlin interop. */
+    public boolean waitFor(String message, Predicate<WindowManagerState> waitCondition) {
+        return waitFor(waitCondition, message);
+    }
+
     /** @return {@code true} if the wait is successful; {@code false} if timeout occurs. */
-    boolean waitFor(Predicate<WindowManagerState> waitCondition, String message) {
+    public boolean waitFor(Predicate<WindowManagerState> waitCondition, String message) {
         return Condition.waitFor(message, () -> {
             computeState();
             return waitCondition.test(this);
@@ -321,7 +336,7 @@
         return false;
     }
 
-    void waitAndAssertAppFocus(String appPackageName, long waitTime) {
+    public void waitAndAssertAppFocus(String appPackageName, long waitTime) {
         final Condition<String> condition = new Condition<>(appPackageName + " to be focused");
         Condition.waitFor(condition.setResultSupplier(() -> {
             computeState();
@@ -434,17 +449,17 @@
         return false;
     }
 
-    private boolean shouldWaitForSanityCheck() {
+    private boolean shouldWaitForValidityCheck() {
         try {
-            assertSanity();
+            assertValidity();
         } catch (Throwable t) {
-            logAlways("Waiting for sanity check: " + t.toString());
+            logAlways("Waiting for validity check: " + t.toString());
             return true;
         }
         return false;
     }
 
-    void assertSanity() {
+    void assertValidity() {
         assertThat("Must have stacks", getStackCount(), greaterThan(0));
         // TODO: Update when keyguard will be shown on multiple displays
         if (!getKeyguardControllerState().keyguardShowing) {
@@ -465,11 +480,11 @@
         assertNotNull("Must have app.", getFocusedApp());
     }
 
-    void assertContainsStack(String msg, int windowingMode, int activityType) {
+    public void assertContainsStack(String msg, int windowingMode, int activityType) {
         assertTrue(msg, containsStack(windowingMode, activityType));
     }
 
-    void assertDoesNotContainStack(String msg, int windowingMode, int activityType) {
+    public void assertDoesNotContainStack(String msg, int windowingMode, int activityType) {
         assertFalse(msg, containsStack(windowingMode, activityType));
     }
 
@@ -477,7 +492,8 @@
         assertFrontStackOnDisplay(msg, windowingMode, activityType, DEFAULT_DISPLAY);
     }
 
-    void assertFrontStackOnDisplay(String msg, int windowingMode, int activityType, int displayId) {
+    public void assertFrontStackOnDisplay(String msg, int windowingMode, int activityType,
+            int displayId) {
         if (windowingMode != WINDOWING_MODE_UNDEFINED) {
             assertEquals(msg, windowingMode,
                     getFrontStackWindowingMode(displayId));
@@ -487,7 +503,7 @@
         }
     }
 
-    void assertFrontStackActivityType(String msg, int activityType) {
+    public void assertFrontStackActivityType(String msg, int activityType) {
         assertEquals(msg, activityType, getFrontStackActivityType(DEFAULT_DISPLAY));
     }
 
@@ -510,13 +526,13 @@
         assertEquals(msg, activityComponentName, getFocusedApp());
     }
 
-    void assertFocusedAppOnDisplay(final String msg, final ComponentName activityName,
+    public void assertFocusedAppOnDisplay(final String msg, final ComponentName activityName,
             final int displayId) {
         final String activityComponentName = getActivityName(activityName);
         assertEquals(msg, activityComponentName, getDisplay(displayId).getFocusedApp());
     }
 
-    void assertNotFocusedActivity(String msg, ComponentName activityName) {
+    public void assertNotFocusedActivity(String msg, ComponentName activityName) {
         assertNotEquals(msg, getFocusedActivity(), getActivityName(activityName));
         assertNotEquals(msg, getFocusedApp(), getActivityName(activityName));
     }
@@ -540,19 +556,19 @@
         }
     }
 
-    void assertNotResumedActivity(String msg, ComponentName activityName) {
+    public void assertNotResumedActivity(String msg, ComponentName activityName) {
         assertNotEquals(msg, getFocusedActivity(), getActivityName(activityName));
     }
 
-    void assertFocusedWindow(String msg, String windowName) {
+    public void assertFocusedWindow(String msg, String windowName) {
         assertEquals(msg, windowName, getFocusedWindow());
     }
 
-    void assertNotFocusedWindow(String msg, String windowName) {
+    public void assertNotFocusedWindow(String msg, String windowName) {
         assertNotEquals(msg, getFocusedWindow(), windowName);
     }
 
-    void assertNotExist(final ComponentName activityName) {
+    public void assertNotExist(final ComponentName activityName) {
         final String windowName = getWindowName(activityName);
         assertFalse("Activity=" + getActivityName(activityName) + " must NOT exist.",
                 containsActivity(activityName));
@@ -582,7 +598,7 @@
                 visible, isWindowSurfaceShown(windowName));
     }
 
-    void assertHomeActivityVisible(boolean visible) {
+    public void assertHomeActivityVisible(boolean visible) {
         final ComponentName homeActivity = getHomeActivityName();
         assertNotNull(homeActivity);
         assertVisibility(homeActivity, visible);
@@ -591,7 +607,7 @@
     /**
      * Asserts that the device default display minimim width is larger than the minimum task width.
      */
-    void assertDeviceDefaultDisplaySize(String errorMessage) {
+    void assertDeviceDefaultDisplaySizeForMultiWindow(String errorMessage) {
         computeState();
         final int minTaskSizePx = defaultMinimalTaskSize(DEFAULT_DISPLAY);
         final WindowManagerState.DisplayContent display = getDisplay(DEFAULT_DISPLAY);
@@ -601,6 +617,20 @@
         }
     }
 
+    /**
+     * Asserts that the device default display minimum width is not smaller than the minimum width
+     * for split-screen required by CDD.
+     */
+    void assertDeviceDefaultDisplaySizeForSplitScreen(String errorMessage) {
+        computeState();
+        final int minDisplaySizePx = defaultMinimalDisplaySizeForSplitScreen(DEFAULT_DISPLAY);
+        final WindowManagerState.DisplayContent display = getDisplay(DEFAULT_DISPLAY);
+        final Rect displayRect = display.getDisplayRect();
+        if (Math.max(displayRect.width(), displayRect.height()) < minDisplaySizePx) {
+            fail(errorMessage);
+        }
+    }
+
     public void assertKeyguardShowingAndOccluded() {
         assertTrue("Keyguard is showing",
                 getKeyguardControllerState().keyguardShowing);
@@ -659,7 +689,7 @@
 
     public void assertWindowDisplayed(final String windowName) {
         waitForValidState(WaitForValidActivityState.forWindow(windowName));
-        assertTrue(windowName + "is visible", isWindowSurfaceShown(windowName));
+        assertTrue(windowName + " is visible", isWindowSurfaceShown(windowName));
     }
 
     void waitAndAssertImeWindowShownOnDisplay(int displayId) {
diff --git a/tests/inputmethod/Android.bp b/tests/inputmethod/Android.bp
index 909ad0e..86d78e2 100644
--- a/tests/inputmethod/Android.bp
+++ b/tests/inputmethod/Android.bp
@@ -28,10 +28,13 @@
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "CtsMockInputMethodLib",
+        "CtsMockSpellCheckerLib",
         "testng",
+        "kotlin-test",
     ],
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
         "src/**/I*.aidl",
     ],
     aidl: {
diff --git a/tests/inputmethod/AndroidManifest.xml b/tests/inputmethod/AndroidManifest.xml
index 3c754de..0c9371c 100644
--- a/tests/inputmethod/AndroidManifest.xml
+++ b/tests/inputmethod/AndroidManifest.xml
@@ -16,65 +16,62 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.view.inputmethod.cts"
-    android:targetSandboxVersion="2">
+     package="android.view.inputmethod.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-    <application
-        android:label="CtsInputMethodTestCases"
-        android:multiArch="true"
-        android:supportsRtl="true">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <application android:label="CtsInputMethodTestCases"
+         android:multiArch="true"
+         android:supportsRtl="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name="android.view.inputmethod.cts.InputMethodCtsActivity"
-            android:label="InputMethodCtsActivity">
+        <activity android:name="android.view.inputmethod.cts.InputMethodCtsActivity"
+             android:label="InputMethodCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="android.view.inputmethod.cts.util.TestActivity"
-            android:label="TestActivity">
+        <activity android:name="android.view.inputmethod.cts.util.TestActivity"
+             android:label="TestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="android.view.inputmethod.cts.util.StateInitializeActivity"
-            android:label="StateInitializeActivity">
+        <activity android:name="android.view.inputmethod.cts.util.StateInitializeActivity"
+             android:label="StateInitializeActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <!--
-          In order to test window-focus-stealing from other process, let this service run in a
-          separate process. -->
+                      In order to test window-focus-stealing from other process, let this service run in a
+                      separate process. -->
         <service android:name="android.view.inputmethod.cts.util.WindowFocusStealerService"
-            android:process=":focusstealer"
-            android:exported="false">
+             android:process=":focusstealer"
+             android:exported="false">
         </service>
 
         <service android:name="android.view.inputmethod.cts.util.WindowFocusHandleService"
-                 android:exported="false">
+             android:exported="false">
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests of android.view.inputmethod"
-        android:targetPackage="android.view.inputmethod.cts">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests of android.view.inputmethod"
+         android:targetPackage="android.view.inputmethod.cts">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index fd2ea5d..9108a44 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -36,6 +36,15 @@
         <option name="force-install-mode" value="FULL"/>
         <option name="test-file-name" value="CtsMockInputMethod.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <!--
+            MockSpellChecker always needs to be instaleld as a full package, even when CTS is
+            running for instant apps.
+        -->
+        <option name="force-install-mode" value="FULL"/>
+        <option name="test-file-name" value="CtsMockSpellChecker.apk" />
+    </target_preparer>
     <!--
         TODO(yukawa): come up with a proper way to take care of devices that do not support
         installable IMEs.  Ideally target_preparer should have an option to annotate required
diff --git a/tests/inputmethod/mockime/Android.bp b/tests/inputmethod/mockime/Android.bp
index 58849e4..ce60256 100644
--- a/tests/inputmethod/mockime/Android.bp
+++ b/tests/inputmethod/mockime/Android.bp
@@ -41,6 +41,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     static_libs: [
         "androidx.annotation_annotation",
diff --git a/tests/inputmethod/mockime/AndroidManifest.xml b/tests/inputmethod/mockime/AndroidManifest.xml
index 83d8f3f..5978c17 100644
--- a/tests/inputmethod/mockime/AndroidManifest.xml
+++ b/tests/inputmethod/mockime/AndroidManifest.xml
@@ -16,31 +16,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.mockime">
+     package="com.android.cts.mockime">
 
-    <application
-        android:multiArch="true"
-        android:supportsRtl="true">
+    <application android:multiArch="true"
+         android:supportsRtl="true">
 
-        <meta-data android:name="instantapps.clients.allowed" android:value="true" />
+        <meta-data android:name="instantapps.clients.allowed"
+             android:value="true"/>
 
-        <service
-            android:name="com.android.cts.mockime.MockIme"
-            android:label="Mock IME"
-            android:permission="android.permission.BIND_INPUT_METHOD">
+        <service android:name="com.android.cts.mockime.MockIme"
+             android:label="Mock IME"
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
-            <meta-data
-                android:name="android.view.im"
-                android:resource="@xml/method" />
+            <meta-data android:name="android.view.im"
+                 android:resource="@xml/method"/>
         </service>
 
-        <provider
-            android:authorities="com.android.cts.mockime.provider"
-            android:name="com.android.cts.mockime.SettingsProvider"
-            android:exported="true"
-            android:visibleToInstantApps="true">
+        <provider android:authorities="com.android.cts.mockime.provider"
+             android:name="com.android.cts.mockime.SettingsProvider"
+             android:exported="true"
+             android:visibleToInstantApps="true">
         </provider>
 
     </application>
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
index 4e62194..28f5c5a 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -25,6 +25,7 @@
 
 import java.util.Optional;
 import java.util.concurrent.TimeoutException;
+import java.util.function.BooleanSupplier;
 import java.util.function.Predicate;
 
 /**
@@ -160,6 +161,15 @@
     }
 
     /**
+     * Returns a matcher to check if the {@code name} is from
+     * {@code MockIme.Tracer#onVerify(String, BooleanSupplier)}
+     */
+    public static Predicate<ImeEvent> verificationMatcher(@NonNull String name) {
+        return event -> "onVerify".equals(event.getEventName())
+                && name.equals(event.getArguments().getString("name"));
+    }
+
+    /**
     * Checks if {@code eventName} has occurred on the EditText(or TextView) of the current
     * activity.
     * @param eventName event name to check
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
index 6731e85..fdeedfc 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -53,6 +53,7 @@
     private static final String INLINE_SUGGESTION_VIEW_CONTENT_DESC =
             "InlineSuggestionViewContentDesc";
     private static final String STRICT_MODE_ENABLED = "StrictModeEnabled";
+    private static final String VERIFY_GET_DISPLAY_ON_CREATE = "VerifyGetDisplayOnCreate";
 
     @NonNull
     private final PersistableBundle mBundle;
@@ -132,6 +133,10 @@
         return mBundle.getBoolean(STRICT_MODE_ENABLED, false);
     }
 
+    public boolean isVerifyGetDisplayOnCreate() {
+        return mBundle.getBoolean(VERIFY_GET_DISPLAY_ON_CREATE, false);
+    }
+
     static Bundle serializeToBundle(@NonNull String eventCallbackActionName,
             @Nullable Builder builder) {
         final Bundle result = new Bundle();
@@ -290,5 +295,14 @@
             mBundle.putBoolean(STRICT_MODE_ENABLED, enabled);
             return this;
         }
+
+        /**
+         * Sets whether to verify {@link android.inputmethodservice.InputMethodService#getDisplay()}
+         * or not.
+         */
+        public Builder setVerifyGetDisplayOnCreate(boolean enabled) {
+            mBundle.putBoolean(VERIFY_GET_DISPLAY_ON_CREATE, enabled);
+            return this;
+        }
     }
 }
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index cb68f93..21f1dc5 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -43,6 +43,8 @@
 import android.util.Log;
 import android.util.Size;
 import android.util.TypedValue;
+import android.view.Display;
+import android.view.GestureDetector;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
@@ -152,6 +154,16 @@
                     throw new IllegalStateException("command " + command
                             + " should be handled on the main thread");
                 }
+                // The context which created from InputMethodService#createXXXContext must behave
+                // like an UI context, which can obtain a display, a window manager,
+                // a view configuration and a gesture detector instance without strict mode
+                // violation.
+                final Configuration testConfig = new Configuration();
+                testConfig.setToDefaults();
+                final Context configContext = createConfigurationContext(testConfig);
+                final Context attrContext = createAttributionContext(null /* attributionTag */);
+                // UI component accesses on a display context must throw strict mode violations.
+                final Context displayContext = createDisplayContext(getDisplay());
                 switch (command.getName()) {
                     case "getTextBeforeCursor": {
                         final int n = command.getExtras().getInt("n");
@@ -311,22 +323,81 @@
                         mInlineSuggestionsExtras = command.getExtras();
                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
                     case "verifyGetDisplay":
-                        Context configContext = createConfigurationContext(new Configuration());
-                        return getDisplay() != null && configContext.getDisplay() != null;
-                    case "verifyGetWindowManager":
-                        configContext = createConfigurationContext(new Configuration());
-                        return getSystemService(WindowManager.class) != null
-                                && configContext.getSystemService(WindowManager.class) != null;
-                    case "verifyGetViewConfiguration":
-                            configContext = createConfigurationContext(new Configuration());
-                            return ViewConfiguration.get(this) != null
-                                    && ViewConfiguration.get(configContext) != null;
+                        try {
+                            return verifyGetDisplay();
+                        } catch (UnsupportedOperationException e) {
+                            return e;
+                        }
+                    case "verifyGetWindowManager": {
+                        final WindowManager imsWm = getSystemService(WindowManager.class);
+                        final WindowManager configContextWm =
+                                configContext.getSystemService(WindowManager.class);
+                        final WindowManager attrContextWm =
+                                attrContext.getSystemService(WindowManager.class);
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "verifyGetViewConfiguration": {
+                        final ViewConfiguration imsViewConfig = ViewConfiguration.get(this);
+                        final ViewConfiguration configContextViewConfig =
+                                ViewConfiguration.get(configContext);
+                        final ViewConfiguration attrContextViewConfig =
+                                ViewConfiguration.get(attrContext);
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "verifyGetGestureDetector": {
+                        GestureDetector.SimpleOnGestureListener listener =
+                                new GestureDetector.SimpleOnGestureListener();
+                        final GestureDetector imsGestureDetector =
+                                new GestureDetector(this, listener);
+                        final GestureDetector configContextGestureDetector =
+                                new GestureDetector(configContext, listener);
+                        final GestureDetector attrGestureDetector =
+                                new GestureDetector(attrContext, listener);
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "verifyGetWindowManagerOnDisplayContext": {
+                        // Obtaining a WindowManager on a display context must throw a strict mode
+                        // violation.
+                        final WindowManager wm = displayContext
+                                .getSystemService(WindowManager.class);
+
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "verifyGetViewConfigurationOnDisplayContext": {
+                        // Obtaining a ViewConfiguration on a display context must throw a strict
+                        // mode violation.
+                        final ViewConfiguration viewConfiguration =
+                                ViewConfiguration.get(displayContext);
+
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "verifyGetGestureDetectorOnDisplayContext": {
+                        // Obtaining a GestureDetector on a display context must throw a strict mode
+                        // violation.
+                        GestureDetector.SimpleOnGestureListener listener =
+                                new GestureDetector.SimpleOnGestureListener();
+                        final GestureDetector gestureDetector =
+                                new GestureDetector(displayContext, listener);
+
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
                 }
             }
             return ImeEvent.RETURN_VALUE_UNAVAILABLE;
         });
     }
 
+    private boolean verifyGetDisplay() throws UnsupportedOperationException {
+        final Display display;
+        final Display configContextDisplay;
+        final Configuration config = new Configuration();
+        config.setToDefaults();
+        final Context configContext = createConfigurationContext(config);
+        display = getDisplay();
+        configContextDisplay = configContext.getDisplay();
+        return display != null && configContextDisplay != null;
+    }
+
     @Nullable
     private Bundle mInlineSuggestionsExtras;
 
@@ -414,7 +485,9 @@
             } else {
                 registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler);
             }
-
+            if (mSettings.isVerifyGetDisplayOnCreate()) {
+                getTracer().onVerify("getDisplay", this::verifyGetDisplay);
+            }
             final int windowFlags = mSettings.getWindowFlags(0);
             final int windowFlagsMask = mSettings.getWindowFlagsMask(0);
             if (windowFlags != 0 || windowFlagsMask != 0) {
@@ -677,6 +750,15 @@
                 () -> super.onUpdateCursorAnchorInfo(cursorAnchorInfo));
     }
 
+    @Override
+    public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
+            int candidatesStart, int candidatesEnd) {
+        getTracer().onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+                candidatesStart, candidatesEnd,
+                () -> super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+                        candidatesStart, candidatesEnd));
+    }
+
     @CallSuper
     public boolean onEvaluateInputViewShown() {
         return getTracer().onEvaluateInputViewShown(() -> {
@@ -936,6 +1018,12 @@
             recordEventInternal("onCreate", runnable);
         }
 
+        void onVerify(String name, @NonNull BooleanSupplier supplier) {
+            final Bundle arguments = new Bundle();
+            arguments.putString("name", name);
+            recordEventInternal("onVerify", supplier::getAsBoolean, arguments);
+        }
+
         void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly,
                 @NonNull Runnable runnable) {
             final Bundle arguments = new Bundle();
@@ -1001,6 +1089,23 @@
             recordEventInternal("onUpdateCursorAnchorInfo", runnable, arguments);
         }
 
+        void onUpdateSelection(int oldSelStart,
+                int oldSelEnd,
+                int newSelStart,
+                int newSelEnd,
+                int candidatesStart,
+                int candidatesEnd,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("oldSelStart", oldSelStart);
+            arguments.putInt("oldSelEnd", oldSelEnd);
+            arguments.putInt("newSelStart", newSelStart);
+            arguments.putInt("newSelEnd", newSelEnd);
+            arguments.putInt("candidatesStart", candidatesStart);
+            arguments.putInt("candidatesEnd", candidatesEnd);
+            recordEventInternal("onUpdateSelection", runnable, arguments);
+        }
+
         boolean onShowInputRequested(int flags, boolean configChange,
                 @NonNull BooleanSupplier supplier) {
             final Bundle arguments = new Bundle();
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 9a5eba8..aedaa1f 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -1044,4 +1044,24 @@
     public ImeCommand callVerifyGetViewConfiguration() {
         return callCommandInternal("verifyGetViewConfiguration", new Bundle());
     }
+
+    @NonNull
+    public ImeCommand callVerifyGetGestureDetector() {
+        return callCommandInternal("verifyGetGestureDetector", new Bundle());
+    }
+
+    @NonNull
+    public ImeCommand callVerifyGetWindowManagerOnDisplayContext() {
+        return callCommandInternal("verifyGetWindowManagerOnDisplayContext", new Bundle());
+    }
+
+    @NonNull
+    public ImeCommand callVerifyGetViewConfigurationOnDisplayContext() {
+        return callCommandInternal("verifyGetViewConfigurationOnDisplayContext", new Bundle());
+    }
+
+    @NonNull
+    public ImeCommand callVerifyGetGestureDetectorOnDisplayContext() {
+        return callCommandInternal("verifyGetGestureDetectorOnDisplayContext", new Bundle());
+    }
 }
diff --git a/tests/inputmethod/mockspellchecker/Android.bp b/tests/inputmethod/mockspellchecker/Android.bp
new file mode 100644
index 0000000..dce4215
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_helper_library {
+    name: "CtsMockSpellCheckerLib",
+    sdk_version: "test_current",
+
+    srcs: [
+        "src/**/*.kt",
+        "src/**/*.proto",
+    ],
+    libs: ["junit"],
+    proto: {
+        type: "lite",
+    },
+    static_libs: [
+        "androidx.annotation_annotation",
+        "compatibility-device-util-axt",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsMockSpellChecker",
+    defaults: ["cts_defaults"],
+    optimize: {
+        enabled: false,
+    },
+    sdk_version: "current",
+    min_sdk_version: "19",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "CtsMockSpellCheckerLib",
+    ],
+}
diff --git a/tests/inputmethod/mockspellchecker/AndroidManifest.xml b/tests/inputmethod/mockspellchecker/AndroidManifest.xml
new file mode 100644
index 0000000..f076bb1
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.mockspellchecker">
+
+    <application android:multiArch="true"
+                 android:supportsRtl="true">
+
+        <meta-data android:name="instantapps.clients.allowed"
+                   android:value="true"/>
+
+        <service android:name=".MockSpellChecker"
+                 android:label="@string/spell_checker_name"
+                 android:permission="android.permission.BIND_TEXT_SERVICE"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.service.textservice.SpellCheckerService"/>
+            </intent-filter>
+
+            <meta-data
+                android:name="android.view.textservice.scs"
+                android:resource="@xml/spellchecker"/>
+        </service>
+
+        <provider android:authorities="com.android.cts.mockspellchecker.provider"
+                  android:name=".SharedPrefsProvider"
+                  android:exported="true"
+                  android:visibleToInstantApps="true">
+        </provider>
+
+    </application>
+</manifest>
diff --git a/tests/inputmethod/mockspellchecker/res/values/values.xml b/tests/inputmethod/mockspellchecker/res/values/values.xml
new file mode 100644
index 0000000..4accba9
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <string name="spell_checker_name">Mock Spell Checker</string>
+</resources>
diff --git a/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml b/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
new file mode 100644
index 0000000..8820f29
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<spell-checker xmlns:android="http://schemas.android.com/apk/res/android"
+    android:label="@string/spell_checker_name">
+    <subtype
+        android:label="English"
+        android:subtypeLocale="en"
+    />
+</spell-checker>
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/Constants.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/Constants.kt
new file mode 100644
index 0000000..8653e94
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/Constants.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.mockspellchecker
+
+const val TAG = "MockSpellChecker"
+const val PACKAGE = "com.android.cts.mockspellchecker"
+const val AUTHORITY = "com.android.cts.mockspellchecker.provider"
+
+internal const val KEY_CONFIGURATION = "configuration"
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
new file mode 100644
index 0000000..ee16ce4
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.mockspellchecker
+
+import android.content.ComponentName
+import android.service.textservice.SpellCheckerService
+import android.util.Log
+import android.view.textservice.SuggestionsInfo
+import android.view.textservice.TextInfo
+import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+internal inline fun <T> withLog(msg: String, block: () -> T): T {
+    Log.i(TAG, msg)
+    return block()
+}
+
+/** Mock Spell checker for end-to-end tests. */
+class MockSpellChecker : SpellCheckerService() {
+
+    override fun onCreate() = withLog("MockSpellChecker.onCreate") {
+        super.onCreate()
+    }
+
+    override fun onDestroy() = withLog("MockSpellChecker.onDestroy") {
+        super.onDestroy()
+    }
+
+    override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) {
+        writer?.println("MockSpellChecker")
+    }
+
+    override fun createSession(): Session = withLog("MockSpellChecker.createSession") {
+        val configuration = MockSpellCheckerConfiguration.parseFrom(
+                SharedPrefsProvider.get(contentResolver, KEY_CONFIGURATION))
+        return MockSpellCheckerSession(configuration)
+    }
+
+    private inner class MockSpellCheckerSession(
+        val configuration: MockSpellCheckerConfiguration
+    ) : SpellCheckerService.Session() {
+
+        override fun onCreate() = withLog("MockSpellCheckerSession.onCreate") {
+        }
+
+        override fun onGetSuggestions(
+            textInfo: TextInfo?,
+            suggestionsLimit: Int
+        ): SuggestionsInfo = withLog(
+            "MockSpellCheckerSession.onGetSuggestions: ${textInfo?.text}") {
+            if (textInfo == null) return emptySuggestionsInfo()
+            return configuration.suggestionRulesList
+                    .find { it.match == textInfo.text }
+                    ?.let { SuggestionsInfo(it.attributes, it.suggestionsList.toTypedArray()) }
+                    ?: emptySuggestionsInfo()
+        }
+
+        private fun emptySuggestionsInfo() = SuggestionsInfo(0, arrayOf())
+    }
+
+    companion object {
+        @JvmStatic
+        fun getId(): String =
+                ComponentName(PACKAGE, MockSpellChecker::class.java.name).flattenToShortString()
+    }
+}
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
new file mode 100644
index 0000000..90c9fd5
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.mockspellchecker
+
+import android.content.Context
+import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
+
+/**
+ * Client interface for {@link MockSpellChecker}.
+ *
+ * <p>This class should be used by test apps.
+ */
+class MockSpellCheckerClient(
+    private val context: Context,
+    private val configuration: MockSpellCheckerConfiguration
+)
+    : AutoCloseable {
+
+    fun initialize() {
+        SharedPrefsProvider.put(
+                context.contentResolver, KEY_CONFIGURATION, configuration.toByteArray())
+    }
+
+    override fun close() {
+        SharedPrefsProvider.delete(context.contentResolver, KEY_CONFIGURATION)
+    }
+
+    companion object {
+        @JvmStatic
+        fun create(context: Context, configuration: MockSpellCheckerConfiguration):
+                MockSpellCheckerClient {
+            val client = MockSpellCheckerClient(context, configuration)
+            client.initialize()
+            return client
+        }
+    }
+}
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/SharedPrefsProvider.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/SharedPrefsProvider.kt
new file mode 100644
index 0000000..7e23885
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/SharedPrefsProvider.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.mockspellchecker
+
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.util.Base64
+
+private const val PREFS_FILE_NAME = "prefs.xml"
+private const val COLUMN_NAME = "value"
+
+/**
+ * ContentProvider to access MockSpellChecker's shared preferences.
+ *
+ * <p>Please use the companion object methods to interact with this ContentProvider. The companion
+ * object methods can be used from other processes.
+ *
+ * <p>This class supports ByteArray value only.
+ */
+class SharedPrefsProvider : ContentProvider() {
+
+    override fun onCreate(): Boolean = withLog("SharedPrefsProvider.onCreate") { true }
+
+    override fun getType(uri: Uri): String? = null
+
+    override fun query(
+        uri: Uri,
+        projection: Array<String>?,
+        selection: String?,
+        selectionArgs: Array<String>?,
+        sortOrder: String?
+    ): Cursor? = withLog("SharedPrefsProvider.query: $uri") {
+        val context = context ?: return null
+        val prefs = getSharedPreferences(context)
+        val bytes = Base64.decode(prefs.getString(uri.path, ""), Base64.DEFAULT)
+        val cursor = MatrixCursor(arrayOf(COLUMN_NAME))
+        cursor.addRow(arrayOf(bytes))
+        return cursor
+    }
+
+    override fun insert(uri: Uri, values: ContentValues?): Uri? =
+            withLog("SharedPrefsProvider.insert: $uri") { null }
+
+    override fun update(
+        uri: Uri,
+        values: ContentValues?,
+        selection: String?,
+        selectionArgs: Array<String>?
+    ): Int = withLog("SharedPrefsProvider.update: $uri") {
+        val context = context ?: return 0
+        if (values == null) return 0
+        val prefs = getSharedPreferences(context)
+        val bytes = values.getAsByteArray(COLUMN_NAME)
+        val str = Base64.encodeToString(bytes, Base64.DEFAULT)
+        prefs.edit().putString(uri.path, str).apply()
+        return 1
+    }
+
+    override fun delete(
+        uri: Uri,
+        selection: String?,
+        selectionArgs: Array<String>?
+    ): Int = withLog("SharedPrefsProvider.delete: $uri") {
+        val context = context ?: return 0
+        val prefs = getSharedPreferences(context)
+        prefs.edit().remove(uri.path).apply()
+        return 1
+    }
+
+    private fun getSharedPreferences(context: Context) =
+        context.getSharedPreferences(PREFS_FILE_NAME, Context.MODE_PRIVATE)
+
+    companion object {
+        /** Returns the data for the key. */
+        fun get(resolver: ContentResolver, key: String): ByteArray {
+            val cursor = resolver.query(uriFor(key), arrayOf(COLUMN_NAME), null, null)
+            return if (cursor != null && cursor.moveToNext()) {
+                cursor.getBlob(0)
+            } else {
+                ByteArray(0)
+            }
+        }
+
+        /** Stores the data for the key. */
+        fun put(resolver: ContentResolver, key: String, value: ByteArray) {
+            val values = ContentValues()
+            values.put(COLUMN_NAME, value)
+            resolver.update(uriFor(key), values, null)
+        }
+
+        /** Deletes the data for the key. */
+        fun delete(resolver: ContentResolver, key: String) {
+            resolver.delete(uriFor(key), null)
+        }
+
+        private fun uriFor(key: String): Uri = Uri.parse("content://$AUTHORITY/$key")
+    }
+}
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
new file mode 100644
index 0000000..58b127f
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package com.android.cts.mockspellchecker;
+
+option java_outer_classname = "MockSpellCheckerProto";
+
+// Represents a suggestion rule.
+// If the string matches 'match', SuggestionsInfo with attributes and suggestions are appended.
+message SuggestionRule {
+  optional string match = 1;
+  optional int32 attributes = 2;
+  repeated string suggestions = 3;
+}
+
+// Represents a MockSpellChecker configuration.
+message MockSpellCheckerConfiguration {
+  repeated SuggestionRule suggestion_rules = 1;
+};
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
index 753d63c..8764273 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -78,6 +78,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -87,6 +88,7 @@
 @RunWith(AndroidJUnit4.class)
 public class FocusHandlingTest extends EndToEndImeTestBase {
     static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    static final long EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
     static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
 
     @Rule
@@ -632,6 +634,30 @@
         }
     }
 
+    @Test
+    public void testOnCheckIsTextEditorRunOnUIThread() throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final CountDownLatch uiThreadSignal = new CountDownLatch(1);
+        try (CloseOnce session = CloseOnce.of(new ServiceSession(instrumentation.getContext()))) {
+            final AtomicBoolean popupTextHasWindowFocus = new AtomicBoolean(false);
+
+            // Create a popupTextView which from Service with different UI thread and set a
+            // countDownLatch to verify onCheckIsTextEditor run on UI thread.
+            final ServiceSession serviceSession = (ServiceSession) session.mAutoCloseable;
+            serviceSession.getService().setUiThreadSignal(uiThreadSignal);
+            final EditText popupTextView = serviceSession.getService().getPopupTextView(
+                    popupTextHasWindowFocus);
+            assertTrue(popupTextView.getHandler().getLooper()
+                    != serviceSession.getService().getMainLooper());
+
+            // Emulate tap event
+            CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, popupTextView);
+
+            // Wait until the UI thread countDownLatch reach to 0 or timeout
+            assertTrue(uiThreadSignal.await(EXPECT_TIMEOUT, TimeUnit.MILLISECONDS));
+        }
+    }
+
     private static class ServiceSession implements ServiceConnection, AutoCloseable {
         private final Context mContext;
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
index d104450..6656d42 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
@@ -18,9 +18,12 @@
 
 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
 
@@ -32,8 +35,8 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
-import android.content.Context;
 import android.content.Intent;
+import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.os.SystemClock;
@@ -42,7 +45,6 @@
 import android.view.Gravity;
 import android.view.View;
 import android.view.WindowInsets;
-import android.view.WindowInsetsController;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
@@ -69,7 +71,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -110,7 +111,6 @@
             CtsTouchUtils.emulateTapOnViewCenter(
                     InstrumentationRegistry.getInstrumentation(), null, editText);
             TestUtils.waitOnMainUntil(() -> editText.hasFocus(), TIMEOUT);
-            WindowInsetsController controller = editText.getWindowInsetsController();
 
             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
             expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
@@ -123,7 +123,16 @@
 
             final View[] childViewRoot = new View[1];
             TestUtils.runOnMainSync(() -> {
-                childViewRoot[0] = addChildWindow(activity);
+                final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+                attrs.token = activity.getWindow().getAttributes().token;
+                attrs.type = TYPE_APPLICATION;
+                attrs.width = 200;
+                attrs.height = 200;
+                attrs.format = PixelFormat.TRANSPARENT;
+                attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
+                attrs.setFitInsetsTypes(WindowInsets.Type.ime() | WindowInsets.Type.statusBars()
+                        | WindowInsets.Type.navigationBars());
+                childViewRoot[0] = addChildWindow(activity, attrs);
                 childViewRoot[0].setVisibility(View.VISIBLE);
             });
             TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
@@ -135,6 +144,104 @@
         }
     }
 
+    @Test
+    public void testImeVisibilityWhenImeFocusableGravityBottomChildPopup() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setInputViewHeight(NEW_KEYBOARD_HEIGHT))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final Pair<EditText, TestActivity> editTextTestActivityPair =
+                    launchTestActivity(false, marker);
+            final EditText editText = editTextTestActivityPair.first;
+            final TestActivity activity = editTextTestActivityPair.second;
+
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectImeInvisible(TIMEOUT);
+
+            // Emulate tap event
+            CtsTouchUtils.emulateTapOnViewCenter(
+                    InstrumentationRegistry.getInstrumentation(), null, editText);
+            TestUtils.waitOnMainUntil(editText::hasFocus, TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            PollingCheck.check("Ime insets should be visible", TIMEOUT,
+                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
+            expectImeVisible(TIMEOUT);
+
+            final View[] childViewRoot = new View[1];
+            TestUtils.runOnMainSync(() -> {
+                final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+                attrs.type = TYPE_APPLICATION_PANEL;
+                attrs.width = MATCH_PARENT;
+                attrs.height = NEW_KEYBOARD_HEIGHT;
+                attrs.gravity = Gravity.BOTTOM;
+                attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
+                childViewRoot[0] = addChildWindow(activity, attrs);
+                childViewRoot[0].setBackgroundColor(Color.RED);
+                childViewRoot[0].setVisibility(View.VISIBLE);
+            });
+            // The window will be shown above (in y-axis) the IME.
+            TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
+                    && childViewRoot[0].getVisibility() == View.VISIBLE, TIMEOUT);
+            // IME should be on screen without reset.
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            PollingCheck.check("Ime insets should be visible", TIMEOUT,
+                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
+            expectImeVisible(TIMEOUT);
+        }
+    }
+
+    @Test
+    public void testImeVisibilityWhenImeFocusableChildPopupOverlaps() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setInputViewHeight(NEW_KEYBOARD_HEIGHT))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final Pair<EditText, TestActivity> editTextTestActivityPair =
+                    launchTestActivity(false, marker);
+            final EditText editText = editTextTestActivityPair.first;
+            final TestActivity activity = editTextTestActivityPair.second;
+
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectImeInvisible(TIMEOUT);
+
+            // Emulate tap event
+            CtsTouchUtils.emulateTapOnViewCenter(
+                    InstrumentationRegistry.getInstrumentation(), null, editText);
+            TestUtils.waitOnMainUntil(editText::hasFocus, TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            PollingCheck.check("Ime insets should be visible", TIMEOUT,
+                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
+            expectImeVisible(TIMEOUT);
+
+            final View[] childViewRoot = new View[1];
+            TestUtils.runOnMainSync(() -> {
+                final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+                attrs.type = TYPE_APPLICATION_PANEL;
+                attrs.width = MATCH_PARENT;
+                attrs.height = NEW_KEYBOARD_HEIGHT;
+                attrs.gravity = Gravity.BOTTOM;
+                attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM | FLAG_LAYOUT_IN_SCREEN;
+                childViewRoot[0] = addChildWindow(activity, attrs);
+                childViewRoot[0].setBackgroundColor(Color.RED);
+                childViewRoot[0].setVisibility(View.VISIBLE);
+            });
+            // The window will be shown behind (in z-axis) the IME.
+            TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
+                    && childViewRoot[0].getVisibility() == View.VISIBLE, TIMEOUT);
+            // IME should be on screen without reset.
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            PollingCheck.check("Ime insets should be visible", TIMEOUT,
+                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
+            expectImeVisible(TIMEOUT);
+        }
+    }
+
     @AppModeFull(reason = "Instant apps cannot rely on ACTION_CLOSE_SYSTEM_DIALOGS")
     @Test
     public void testEditTextPositionAndPersistWhenAboveImeWindowShown() throws Exception {
@@ -246,19 +353,9 @@
         return new Pair<>(focusedEditTextRef.get(), testActivityRef.get());
     }
 
-    private View addChildWindow(Activity activity) {
-        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        final WindowManager wm = context.getSystemService(WindowManager.class);
-        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
-        attrs.token = activity.getWindow().getAttributes().token;
-        attrs.type = TYPE_APPLICATION;
-        attrs.width = 200;
-        attrs.height = 200;
-        attrs.format = PixelFormat.TRANSPARENT;
-        attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
-        attrs.setFitInsetsTypes(WindowInsets.Type.ime() | WindowInsets.Type.statusBars()
-                | WindowInsets.Type.navigationBars());
-        final View childViewRoot = new View(context);
+    private View addChildWindow(Activity activity, WindowManager.LayoutParams attrs) {
+        final WindowManager wm = activity.getSystemService(WindowManager.class);
+        final View childViewRoot = new View(activity);
         childViewRoot.setVisibility(View.GONE);
         wm.addView(childViewRoot, attrs);
         return childViewRoot;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java
new file mode 100644
index 0000000..f498e4a
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.EventFilterMode.CHECK_ALL;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.clearAllEvents;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
+import android.os.StrictMode;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.annotation.IntDef;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.mockime.ImeEvent;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for verifying {@link StrictMode} violations on {@link InputMethodService} APIs. */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class InputMethodServiceStrictModeTest extends EndToEndImeTestBase {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+
+    /**
+     * Verifies if get {@link android.view.WindowManager} from {@link InputMethodService} and
+     * context created from {@link InputMethodService#createConfigurationContext(Configuration)}
+     * violates incorrect context violation.
+     *
+     * @see Context#getSystemService(String)
+     * @see Context#getSystemService(Class)
+     */
+    private static final int VERIFY_MODE_GET_WINDOW_MANAGER = 1;
+    /**
+     * Verifies if passing {@link InputMethodService} and context created
+     * from {@link InputMethodService#createConfigurationContext(Configuration)} to
+     * {@link android.view.ViewConfiguration#get(Context)} violates incorrect context violation.
+     */
+    private static final int VERIFY_MODE_GET_VIEW_CONFIGURATION = 2;
+    /**
+     * Verifies if passing {@link InputMethodService} and context created
+     * from {@link InputMethodService#createConfigurationContext(Configuration)} to
+     * {@link android.view.GestureDetector} constructor violates incorrect context violation.
+     */
+    private static final int VERIFY_MODE_GET_GESTURE_DETECTOR = 3;
+
+    /**
+     * Verify mode to verifying if APIs violates incorrect context violation.
+     *
+     * @see #VERIFY_MODE_GET_WINDOW_MANAGER
+     * @see #VERIFY_MODE_GET_VIEW_CONFIGURATION
+     * @see #VERIFY_MODE_GET_GESTURE_DETECTOR
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            VERIFY_MODE_GET_WINDOW_MANAGER,
+            VERIFY_MODE_GET_VIEW_CONFIGURATION,
+            VERIFY_MODE_GET_GESTURE_DETECTOR,
+    })
+    private @interface VerifyMode {}
+
+    @Test
+    public void testIncorrectContextUseOnGetSystemService() throws Exception {
+        verifyIms(VERIFY_MODE_GET_WINDOW_MANAGER);
+    }
+
+    @Test
+    public void testIncorrectContextUseOnGetViewConfiguration() throws Exception {
+        verifyIms(VERIFY_MODE_GET_VIEW_CONFIGURATION);
+    }
+
+    @Test
+    public void testIncorrectContextUseOnGetGestureDetector() throws Exception {
+        verifyIms(VERIFY_MODE_GET_GESTURE_DETECTOR);
+    }
+
+    /**
+     * Verify if APIs violates incorrect context violations by {@code mode}.
+     *
+     * @see VerifyMode
+     */
+    private void verifyIms(@VerifyMode int mode) throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setStrictModeEnabled(true))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+
+            final ImeEventStream forkedStream = clearAllEvents(stream, "onStrictModeViolated");
+            switch (mode) {
+                case VERIFY_MODE_GET_WINDOW_MANAGER:
+                    expectCommand(forkedStream, imeSession.callVerifyGetWindowManager(), TIMEOUT);
+                    break;
+                case VERIFY_MODE_GET_VIEW_CONFIGURATION:
+                    expectCommand(forkedStream,
+                            imeSession.callVerifyGetViewConfiguration(), TIMEOUT);
+                    break;
+                case VERIFY_MODE_GET_GESTURE_DETECTOR:
+                    expectCommand(forkedStream, imeSession.callVerifyGetGestureDetector(), TIMEOUT);
+                    break;
+                default:
+                    // do nothing here.
+                    break;
+            }
+            notExpectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
+                    EXPECTED_TIMEOUT);
+        }
+    }
+
+    /**
+     * Test if UI component accesses from display context derived from {@link InputMethodService}
+     * throw strict mode violations.
+     */
+    @Test
+    public void testIncorrectContextUseOnImsDerivedDisplayContext() throws Exception{
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setStrictModeEnabled(true))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+
+            // Verify if obtaining a WindowManager on an InputMethodService derived display context
+            // throws a strict mode violation.
+            ImeEventStream forkedStream = clearAllEvents(stream, "onStrictModeViolated");
+            expectCommand(forkedStream, imeSession.callVerifyGetWindowManagerOnDisplayContext(),
+                    TIMEOUT);
+
+            expectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
+                   CHECK_ALL, TIMEOUT);
+
+            // Verify if obtaining a ViewConfiguration on an InputMethodService derived display
+            // context throws a strict mode violation.
+            forkedStream = clearAllEvents(stream, "onStrictModeViolated");
+            expectCommand(forkedStream, imeSession.callVerifyGetViewConfigurationOnDisplayContext(),
+                    TIMEOUT);
+
+            expectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
+                    CHECK_ALL, TIMEOUT);
+
+            // Verify if obtaining a GestureDetector on an InputMethodService derived display
+            // context throws a strict mode violation.
+            forkedStream = clearAllEvents(stream, "onStrictModeViolated");
+            expectCommand(forkedStream, imeSession.callVerifyGetGestureDetectorOnDisplayContext(),
+                    TIMEOUT);
+
+            expectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
+                    CHECK_ALL, TIMEOUT);
+        }
+    }
+
+    private TestActivity createTestActivity(final int windowFlags) {
+        return TestActivity.startSync(activity -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+
+            final EditText editText = new EditText(activity);
+            editText.setText("Editable");
+            layout.addView(editText);
+            editText.requestFocus();
+
+            activity.getWindow().setSoftInputMode(windowFlags);
+            return layout;
+        });
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index 42d841a..b0c9434 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -30,6 +30,7 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.verificationMatcher;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -37,9 +38,12 @@
 import static org.junit.Assert.fail;
 
 import android.app.Instrumentation;
+import android.content.Intent;
 import android.graphics.Matrix;
 import android.inputmethodservice.InputMethodService;
+import android.os.Bundle;
 import android.os.SystemClock;
+import android.support.test.uiautomator.UiObject2;
 import android.text.TextUtils;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -52,7 +56,9 @@
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
 import android.view.inputmethod.cts.util.TestActivity;
 import android.view.inputmethod.cts.util.TestUtils;
+import android.view.inputmethod.cts.util.TestWebView;
 import android.view.inputmethod.cts.util.UnlockScreenRule;
+import android.webkit.WebView;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
@@ -84,7 +90,7 @@
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class InputMethodServiceTest extends EndToEndImeTestBase {
-    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(20);
     private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
 
     @Rule
@@ -329,6 +335,10 @@
                     expectedKeyCode, uptimeStart, uptimeEnd);
             assertSynthesizedSoftwareKeyEvent(keyEvents.get(1), KeyEvent.ACTION_UP,
                     expectedKeyCode, uptimeStart, uptimeEnd);
+            final Bundle arguments = expectEvent(stream,
+                    event -> "onUpdateSelection".equals(event.getEventName()),
+                    TIMEOUT).getArguments();
+            expectOnUpdateSelectionArguments(arguments, 0, 0, 1, 1, -1, -1);
         }
     }
 
@@ -403,4 +413,415 @@
             assertEquals(receivedCursorAnchorInfo, originalCursorAnchorInfo);
         }
     }
+
+    /** Test that no exception is thrown when {@link InputMethodService#getDisplay()} is called */
+    @Test
+    public void testGetDisplay() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                mInstrumentation.getContext(), mInstrumentation.getUiAutomation(),
+                new ImeSettings.Builder().setVerifyGetDisplayOnCreate(true))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            // Verify if getDisplay doesn't throw exception before InputMethodService's
+            // initialization.
+            assertTrue(expectEvent(stream, verificationMatcher("getDisplay"),
+                    CHECK_EXIT_EVENT_ONLY, TIMEOUT).getReturnBooleanValue());
+            createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+            // Verify if getDisplay doesn't throw exception
+            assertTrue(expectCommand(stream, imeSession.callVerifyGetDisplay(), TIMEOUT)
+                    .getReturnBooleanValue());
+        }
+    }
+
+    /** Test the cursor position of {@link EditText} is correct after typing on another activity. */
+    @Test
+    public void testCursorAfterLaunchAnotherActivity() throws Exception {
+        final AtomicReference<EditText> firstEditTextRef = new AtomicReference<>();
+        final int NEW_CURSOR_OFFSET = 6;
+        final String INITIAL_TEXT = "initial";
+        final String COMMIT_MSG = "commit msg";
+        final String SECOND_COMMIT_MSG = "second commit msg";
+
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final String marker =
+                    "testCursorAfterLaunchAnotherActivity()/" + SystemClock.elapsedRealtimeNanos();
+
+            // Launch first test activity
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final EditText editText = new EditText(activity);
+                editText.setPrivateImeOptions(marker);
+                editText.setSingleLine(false);
+                firstEditTextRef.set(editText);
+                editText.setText(INITIAL_TEXT);
+                layout.addView(editText);
+                editText.requestFocus();
+                return layout;
+            });
+
+            final EditText firstEditText = firstEditTextRef.get();
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            // Verify onStartInput when first activity launch
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+            final ImeCommand commit = imeSession.callCommitText(COMMIT_MSG, 1);
+            expectCommand(stream, commit, TIMEOUT);
+
+            // Get current position
+            int originalSelectionStart = firstEditText.getSelectionStart();
+            int originalSelectionEnd = firstEditText.getSelectionEnd();
+
+            assertEquals(INITIAL_TEXT.length() + COMMIT_MSG.length(), originalSelectionStart);
+            assertEquals(INITIAL_TEXT.length() + COMMIT_MSG.length(), originalSelectionEnd);
+
+            // Launch second test activity
+            final Intent intent = new Intent()
+                    .setAction(Intent.ACTION_MAIN)
+                    .setClass(InstrumentationRegistry.getInstrumentation().getContext(),
+                            TestActivity.class)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            TestActivity secondActivity = (TestActivity) InstrumentationRegistry
+                    .getInstrumentation().startActivitySync(intent);
+
+            // Commit some messages on second activity
+            final ImeCommand secondCommit = imeSession.callCommitText(SECOND_COMMIT_MSG, 1);
+            expectCommand(stream, secondCommit, TIMEOUT);
+
+            // Back to first activity
+            runOnMainSync(secondActivity::onBackPressed);
+
+            // Make sure TestActivity#onBackPressed() is called.
+            TestUtils.waitOnMainUntil(() -> secondActivity.getOnBackPressedCallCount() > 0,
+                    TIMEOUT, "Activity#onBackPressed() should be called");
+
+            // Update cursor to a new position
+            int newCursorPosition = originalSelectionStart - NEW_CURSOR_OFFSET;
+            final ImeCommand setSelection =
+                    imeSession.callSetSelection(newCursorPosition, newCursorPosition);
+            expectCommand(stream, setSelection, TIMEOUT);
+
+            // Commit to first activity again
+            final ImeCommand commitFirstAgain = imeSession.callCommitText(COMMIT_MSG, 1);
+            expectCommand(stream, commitFirstAgain, TIMEOUT);
+
+            // get new position
+            int newSelectionStart = firstEditText.getSelectionStart();
+            int newSelectionEnd = firstEditText.getSelectionEnd();
+
+            assertEquals(newSelectionStart, newCursorPosition + COMMIT_MSG.length());
+            assertEquals(newSelectionEnd, newCursorPosition + COMMIT_MSG.length());
+        }
+    }
+
+    @Test
+    public void testBatchEdit_commitAndSetComposingRegion_textView() throws Exception {
+        getCommitAndSetComposingRegionTest(TIMEOUT,
+                "testBatchEdit_commitAndSetComposingRegion_textView/")
+                .setTestTextView(true)
+                .runTest();
+    }
+
+    @Test
+    public void testBatchEdit_commitAndSetComposingRegion_webView() throws Exception {
+        getCommitAndSetComposingRegionTest(TIMEOUT,
+                "testBatchEdit_commitAndSetComposingRegion_webView/")
+                .setTestTextView(false)
+                .runTest();
+    }
+
+    @Test
+    public void testBatchEdit_commitSpaceThenSetComposingRegion_textView() throws Exception {
+        getCommitSpaceAndSetComposingRegionTest(TIMEOUT,
+                "testBatchEdit_commitSpaceThenSetComposingRegion_textView/")
+                .setTestTextView(true)
+                .runTest();
+    }
+
+    @Test
+    public void testBatchEdit_commitSpaceThenSetComposingRegion_webView() throws Exception {
+        getCommitSpaceAndSetComposingRegionTest(TIMEOUT,
+                "testBatchEdit_commitSpaceThenSetComposingRegion_webView/")
+                .setTestTextView(false)
+                .runTest();
+    }
+
+    @Test
+    public void testBatchEdit_getCommitSpaceAndSetComposingRegionTestInSelectionTest_textView()
+            throws Exception {
+        getCommitSpaceAndSetComposingRegionInSelectionTest(TIMEOUT,
+                "testBatchEdit_getCommitSpaceAndSetComposingRegionTestInSelectionTest_textView/")
+                .setTestTextView(true)
+                .runTest();
+    }
+
+    @Test
+    public void testBatchEdit_getCommitSpaceAndSetComposingRegionTestInSelectionTest_webView()
+            throws Exception {
+        getCommitSpaceAndSetComposingRegionInSelectionTest(TIMEOUT,
+                "testBatchEdit_getCommitSpaceAndSetComposingRegionTestInSelectionTest_webView/")
+                .setTestTextView(false)
+                .runTest();
+    }
+
+    /** Test case for committing and setting composing region after cursor. */
+    private static UpdateSelectionTest getCommitAndSetComposingRegionTest(
+            long timeout, String makerPrefix) throws Exception {
+        UpdateSelectionTest test = new UpdateSelectionTest(timeout, makerPrefix) {
+            @Override
+            public void testMethodImpl() throws Exception {
+                // "abc|"
+                expectCommand(stream, imeSession.callCommitText("abc", 1), timeout);
+                verifyText("abc", 3, 3);
+                final Bundle arguments1 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments1, 0, 0, 3, 3, -1, -1);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // "|abc"
+                expectCommand(stream, imeSession.callSetSelection(0, 0), timeout);
+                verifyText("abc", 0, 0);
+                final Bundle arguments2 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments2, 3, 3, 0, 0, -1, -1);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // "Back |abc"
+                //        ---
+                expectCommand(stream, imeSession.callBeginBatchEdit(), timeout);
+                expectCommand(stream, imeSession.callCommitText("Back ", 1), timeout);
+                expectCommand(stream, imeSession.callSetComposingRegion(5, 8), timeout);
+                expectCommand(stream, imeSession.callEndBatchEdit(), timeout);
+                verifyText("Back abc", 5, 5);
+                final Bundle arguments3 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments3, 0, 0, 5, 5, 5, 8);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+            }
+        };
+        return test;
+    }
+
+    /** Test case for committing space and setting composing region after cursor. */
+    private static UpdateSelectionTest getCommitSpaceAndSetComposingRegionTest(
+            long timeout, String makerPrefix) throws Exception {
+        UpdateSelectionTest test = new UpdateSelectionTest(timeout, makerPrefix) {
+            @Override
+            public void testMethodImpl() throws Exception {
+                // "Hello|"
+                //  -----
+                expectCommand(stream, imeSession.callSetComposingText("Hello", 1), timeout);
+                verifyText("Hello", 5, 5);
+                final Bundle arguments1 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments1, 0, 0, 5, 5, 0, 5);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // "|Hello"
+                //   -----
+                expectCommand(stream, imeSession.callSetSelection(0, 0), timeout);
+                verifyText("Hello", 0, 0);
+                final Bundle arguments2 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments2, 5, 5, 0, 0, 0, 5);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // " |Hello"
+                //    -----
+                expectCommand(stream, imeSession.callBeginBatchEdit(), timeout);
+                expectCommand(stream, imeSession.callFinishComposingText(), timeout);
+                expectCommand(stream, imeSession.callCommitText(" ", 1), timeout);
+                expectCommand(stream, imeSession.callSetComposingRegion(1, 6), timeout);
+                expectCommand(stream, imeSession.callEndBatchEdit(), timeout);
+
+                verifyText(" Hello", 1, 1);
+                final Bundle arguments3 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments3, 0, 0, 1, 1, 1, 6);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+            }
+        };
+        return test;
+    }
+
+    /**
+     * Test case for committing space in the middle of selection and setting composing region after
+     * cursor.
+     */
+    private static UpdateSelectionTest getCommitSpaceAndSetComposingRegionInSelectionTest(
+            long timeout, String makerPrefix) throws Exception {
+        UpdateSelectionTest test = new UpdateSelectionTest(timeout, makerPrefix) {
+            @Override
+            public void testMethodImpl() throws Exception {
+                // "2005abc|"
+                expectCommand(stream, imeSession.callCommitText("2005abc", 1), timeout);
+                verifyText("2005abc", 7, 7);
+                final Bundle arguments1 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments1, 0, 0, 7, 7, -1, -1);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // "2005|abc"
+                expectCommand(stream, imeSession.callSetSelection(4, 4), timeout);
+                verifyText("2005abc", 4, 4);
+                final Bundle arguments2 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments2, 7, 7, 4, 4, -1, -1);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // "2005 |abc"
+                //        ---
+                expectCommand(stream, imeSession.callBeginBatchEdit(), timeout);
+                expectCommand(stream, imeSession.callCommitText(" ", 1), timeout);
+                expectCommand(stream, imeSession.callSetComposingRegion(5, 8), timeout);
+                expectCommand(stream, imeSession.callEndBatchEdit(), timeout);
+
+                verifyText("2005 abc", 5, 5);
+                final Bundle arguments3 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments3, 4, 4, 5, 5, 5, 8);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+            }
+        };
+        return test;
+    }
+
+    private static void expectOnUpdateSelectionArguments(Bundle arguments,
+            int expectedOldSelStart, int expectedOldSelEnd, int expectedNewSelStart,
+            int expectedNewSelEnd, int expectedCandidateStart, int expectedCandidateEnd) {
+        assertEquals(expectedOldSelStart, arguments.getInt("oldSelStart"));
+        assertEquals(expectedOldSelEnd, arguments.getInt("oldSelEnd"));
+        assertEquals(expectedNewSelStart, arguments.getInt("newSelStart"));
+        assertEquals(expectedNewSelEnd, arguments.getInt("newSelEnd"));
+        assertEquals(expectedCandidateStart, arguments.getInt("candidatesStart"));
+        assertEquals(expectedCandidateEnd, arguments.getInt("candidatesEnd"));
+    }
+
+    /**
+     * Helper class for wrapping tests for {@link android.widget.TextView} and @{@link WebView}
+     * relates to batch edit and update selection change.
+     */
+    private abstract static class UpdateSelectionTest {
+        private final long mTimeout;
+        private final String mMaker;
+        private final AtomicReference<EditText> mEditTextRef = new AtomicReference<>();
+        private final AtomicReference<UiObject2> mInputTextFieldRef = new AtomicReference<>();
+
+        public final MockImeSession imeSession;
+        public final ImeEventStream stream;
+
+        // True if testing TextView, otherwise test WebView
+        private boolean mIsTestingTextView;
+
+        UpdateSelectionTest(long timeout, String makerPrefix) throws Exception {
+            this.mTimeout = timeout;
+            this.mMaker = makerPrefix + SystemClock.elapsedRealtimeNanos();
+            imeSession = MockImeSession.create(
+                    InstrumentationRegistry.getInstrumentation().getContext(),
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                    new ImeSettings.Builder());
+            stream = imeSession.openEventStream();
+        }
+
+        /**
+         * Runs the real test logic, which would test onStartInput event first, then test the logic
+         * in {@link #testMethodImpl()}.
+         *
+         * @throws Exception if timeout or assert fails
+         */
+        public void runTest() throws Exception {
+            if (mIsTestingTextView) {
+                TestActivity.startSync(activity -> {
+                    final LinearLayout layout = new LinearLayout(activity);
+                    layout.setOrientation(LinearLayout.VERTICAL);
+                    final EditText editText = new EditText(activity);
+                    layout.addView(editText);
+                    editText.requestFocus();
+                    editText.setPrivateImeOptions(mMaker);
+                    mEditTextRef.set(editText);
+                    return layout;
+                });
+                assertNotNull(mEditTextRef.get());
+            } else {
+                final UiObject2 inputTextField = TestWebView.launchTestWebViewActivity(
+                        mTimeout, mMaker);
+                assertNotNull("Editor must exists on WebView", inputTextField);
+                mInputTextFieldRef.set(inputTextField);
+                inputTextField.click();
+            }
+            expectEvent(stream, editorMatcher("onStartInput", mMaker), TIMEOUT);
+
+            // Code for testing input connection logic.
+            testMethodImpl();
+        }
+
+        /**
+         * Test method to be overridden by implementation class.
+         */
+        public abstract void testMethodImpl() throws Exception;
+
+        /**
+         * Verifies text and selection range in the edit text if this is running tests for TextView;
+         * otherwise verifies the text (no selection) in the WebView.
+         * @param expectedText expected text in the TextView or WebView
+         * @param selStart expected start position of the selection in the TextView; will be ignored
+         *                 for WebView
+         * @param selEnd expected end position of the selection in the WebView; will be ignored for
+         *               WebView
+         * @throws Exception if timeout or assert fails
+         */
+        public void verifyText(String expectedText, int selStart, int selEnd) throws Exception {
+            if (mIsTestingTextView) {
+                EditText editText = mEditTextRef.get();
+                assertNotNull(editText);
+                waitOnMainUntil(()->
+                        expectedText.equals(editText.getText().toString())
+                                && selStart == editText.getSelectionStart()
+                                && selEnd == editText.getSelectionEnd(), mTimeout);
+            } else {
+                UiObject2 inputTextField = mInputTextFieldRef.get();
+                assertNotNull(inputTextField);
+                waitOnMainUntil(()-> expectedText.equals(inputTextField.getText()), mTimeout);
+            }
+        }
+
+        public UpdateSelectionTest setTestTextView(boolean isTestingTextView) {
+            this.mIsTestingTextView = isTestingTextView;
+            return this;
+        }
+    }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index cad3309..007ce3d 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod.cts;
 
+import static android.content.Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS;
 import static android.view.View.VISIBLE;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -29,6 +30,9 @@
 import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
 import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
 
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
@@ -39,11 +43,17 @@
 
 import android.app.AlertDialog;
 import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Intent;
 import android.graphics.Color;
+import android.net.Uri;
 import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
 import android.support.test.uiautomator.UiObject2;
 import android.text.TextUtils;
 import android.util.Pair;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.WindowInsetsController;
@@ -60,9 +70,14 @@
 
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
 
 import com.android.cts.mockime.ImeEvent;
 import com.android.cts.mockime.ImeEventStream;
@@ -83,6 +98,18 @@
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
     private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
 
+    private static final ComponentName TEST_ACTIVITY = new ComponentName(
+            "android.view.inputmethod.ctstestapp",
+            "android.view.inputmethod.ctstestapp.MainActivity");
+    private static final Uri TEST_ACTIVITY_URI =
+            Uri.parse("https://example.com/android/view/inputmethod/ctstestapp");
+    private static final String EXTRA_KEY_SHOW_DIALOG =
+            "android.view.inputmethod.ctstestapp.EXTRA_KEY_SHOW_DIALOG";
+
+    private static final String ACTION_TRIGGER = "broadcast_action_trigger";
+    private static final String EXTRA_DISMISS_DIALOG = "extra_dismiss_dialog";
+    private static final int NEW_KEYBOARD_HEIGHT = 400;
+
     @Rule
     public final UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
 
@@ -275,20 +302,16 @@
                 InstrumentationRegistry.getInstrumentation().getUiAutomation(),
                 new ImeSettings.Builder())) {
             final ImeEventStream stream = imeSession.openEventStream();
-
+            final String marker = getTestMarker();
             final UiObject2 inputTextField = TestWebView.launchTestWebViewActivity(
-                    TimeUnit.SECONDS.toMillis(5));
+                    TIMEOUT, marker);
             assertNotNull("Editor must exists on WebView", inputTextField);
-
-            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
-            notExpectEvent(stream, event -> "onStartInputView".equals(event.getEventName()),
-                    TIMEOUT);
             expectImeInvisible(TIMEOUT);
 
             inputTextField.click();
             expectEvent(stream.copy(), showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
-            expectEvent(stream.copy(), event -> "onStartInputView".equals(event.getEventName()),
-                    TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
             expectImeVisible(TIMEOUT);
         }
     }
@@ -416,27 +439,27 @@
     }
 
     @Test
-    public void testImeState_EditorDialogLostFocusAfterUnlocked_Unspecified() throws Exception {
+    public void testImeState_Unspecified_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_UNSPECIFIED);
     }
 
     @Test
-    public void testImeState_EditorDialogLostFocusAfterUnlocked_Visible() throws Exception {
+    public void testImeState_Visible_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_VISIBLE);
     }
 
     @Test
-    public void testImeState_EditorDialogLostFocusAfterUnlocked_AlwaysVisible() throws Exception {
+    public void testImeState_AlwaysVisible_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
     }
 
     @Test
-    public void testImeState_EditorDialogLostFocusAfterUnlocked_Hidden() throws Exception {
+    public void testImeState_Hidden_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_HIDDEN);
     }
 
     @Test
-    public void testImeState_EditorDialogLostFocusAfterUnlocked_AlwaysHidden() throws Exception {
+    public void testImeState_AlwaysHidden_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
     }
 
@@ -505,6 +528,118 @@
         }
     }
 
+    @AppModeFull
+    @Test
+    public void testImeVisibilityWhenImeTransitionBetweenActivities_Full() throws Exception {
+        runImeVisibilityWhenImeTransitionBetweenActivities(false /* instant */);
+    }
+
+    @AppModeInstant
+    @Test
+    public void testImeVisibilityWhenImeTransitionBetweenActivities_Instant() throws Exception {
+        runImeVisibilityWhenImeTransitionBetweenActivities(true /* instant */);
+    }
+
+    private void runImeVisibilityWhenImeTransitionBetweenActivities(boolean instant)
+            throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder()
+                        .setInputViewHeight(NEW_KEYBOARD_HEIGHT)
+                        .setDrawsBehindNavBar(true))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+            final String marker = getTestMarker();
+
+            AtomicReference<EditText> editTextRef = new AtomicReference<>();
+            // Launch test activity with focusing editor
+            final TestActivity testActivity =
+                    TestActivity.startSync(activity -> {
+                        final LinearLayout layout = new LinearLayout(activity);
+                        layout.setOrientation(LinearLayout.VERTICAL);
+                        layout.setGravity(Gravity.BOTTOM);
+                        final EditText editText = new EditText(activity);
+                        editTextRef.set(editText);
+                        editText.setHint("focused editText");
+                        editText.setPrivateImeOptions(marker);
+                        editText.requestFocus();
+                        layout.addView(editText);
+                        activity.getWindow().getDecorView().setFitsSystemWindows(true);
+                        activity.getWindow().getDecorView().getWindowInsetsController().show(ime());
+                        return layout;
+                    });
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
+                    View.VISIBLE, TIMEOUT);
+            expectImeVisible(TIMEOUT);
+
+            // Launcher another test activity from another process with popup dialog.
+            launchRemoteDialogActivitySync(TEST_ACTIVITY, instant, TIMEOUT);
+            // Dismiss dialog and back to original test activity
+            triggerActionWithBroadcast(ACTION_TRIGGER, TEST_ACTIVITY.getPackageName(),
+                    EXTRA_DISMISS_DIALOG);
+
+            // Verify keyboard visibility should aligned with IME insets visibility.
+            TestUtils.waitOnMainUntil(
+                    () -> testActivity.getWindow().getDecorView().getVisibility() == VISIBLE
+                            && testActivity.getWindow().getDecorView().hasWindowFocus(), TIMEOUT);
+
+            AtomicReference<Boolean> imeInsetsVisible = new AtomicReference<>();
+            TestUtils.runOnMainSync(() ->
+                    imeInsetsVisible.set(editTextRef.get().getRootWindowInsets().isVisible(ime())));
+
+            if (imeInsetsVisible.get()) {
+                expectImeVisible(TIMEOUT);
+            } else {
+                expectImeInvisible(TIMEOUT);
+            }
+        }
+    }
+
+    private void launchRemoteDialogActivitySync(ComponentName componentName, boolean instant,
+            long timeout) {
+        final StringBuilder commandBuilder = new StringBuilder();
+        if (instant) {
+            final Uri uri = formatStringIntentParam(
+                    TEST_ACTIVITY_URI, EXTRA_KEY_SHOW_DIALOG, "true");
+            commandBuilder.append(String.format("am start -a %s -c %s %s",
+                    Intent.ACTION_VIEW, Intent.CATEGORY_BROWSABLE, uri.toString()));
+        } else {
+            commandBuilder.append("am start -n ").append(componentName.flattenToShortString());
+        }
+
+        runWithShellPermissionIdentity(() -> {
+            runShellCommand(commandBuilder.toString());
+        });
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        BySelector activitySelector = By.pkg(componentName.getPackageName()).depth(0);
+        uiDevice.wait(Until.hasObject(activitySelector), timeout);
+        assertNotNull(uiDevice.findObject(activitySelector));
+    }
+
+    @NonNull
+    private static Uri formatStringIntentParam(@NonNull Uri uri, @NonNull String key,
+            @Nullable String value) {
+        if (value == null) {
+            return uri;
+        }
+        return uri.buildUpon().appendQueryParameter(key, value).build();
+    }
+
+    private void triggerActionWithBroadcast(String action, String receiverPackage, String extra) {
+        final StringBuilder commandBuilder = new StringBuilder();
+        commandBuilder.append("am broadcast -a ").append(action).append(" -p ").append(
+                receiverPackage);
+        commandBuilder.append(" -f 0x").append(
+                Integer.toHexString(FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS));
+        commandBuilder.append(" --ez " + extra + " true");
+        runWithShellPermissionIdentity(() -> {
+            runShellCommand(commandBuilder.toString());
+        });
+    }
+
     private static ImeSettings.Builder getFloatingImeSettings(@ColorInt int navigationBarColor) {
         final ImeSettings.Builder builder = new ImeSettings.Builder();
         builder.setWindowFlags(0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
new file mode 100644
index 0000000..0c40014
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.inputmethod.cts
+
+import android.content.Context
+import android.provider.Settings
+import android.text.style.SuggestionSpan
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.inputmethod.cts.util.EndToEndImeTestBase
+import android.view.inputmethod.cts.util.InputMethodVisibilityVerifier
+import android.view.inputmethod.cts.util.TestActivity
+import android.view.inputmethod.cts.util.TestUtils
+import android.view.inputmethod.cts.util.UnlockScreenRule
+import android.view.textservice.SpellCheckerSubtype
+import android.view.textservice.SuggestionsInfo
+import android.view.textservice.TextServicesManager
+import android.widget.EditText
+import android.widget.LinearLayout
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.CtsTouchUtils
+import com.android.compatibility.common.util.SettingsStateChangerRule
+import com.android.cts.mockime.MockImeSession
+import com.android.cts.mockspellchecker.MockSpellChecker
+import com.android.cts.mockspellchecker.MockSpellCheckerClient
+import com.android.cts.mockspellchecker.MockSpellCheckerProto
+import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.TimeUnit
+
+private val TIMEOUT = TimeUnit.SECONDS.toMillis(5)
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class SpellCheckerTest : EndToEndImeTestBase() {
+
+    private val context: Context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+
+    @Rule
+    fun unlockScreenRule() = UnlockScreenRule()
+
+    @Rule
+    fun spellCheckerSettingsRule() = SettingsStateChangerRule(
+            context, Settings.Secure.SELECTED_SPELL_CHECKER, MockSpellChecker.getId())
+
+    @Rule
+    fun spellCheckerSubtypeSettingsRule() = SettingsStateChangerRule(
+            context, Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE,
+            SpellCheckerSubtype.SUBTYPE_ID_NONE.toString())
+
+    @Before
+    fun setUp() {
+        val tsm = context.getSystemService(TextServicesManager::class.java)!!
+        // Skip if spell checker is not enabled by default.
+        Assume.assumeNotNull(tsm)
+        Assume.assumeTrue(tsm.isSpellCheckerEnabled)
+    }
+
+    @Test
+    fun misspelled() {
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .addSuggestionRules(
+                        MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                .setMatch("match")
+                                .addSuggestions("suggestion")
+                                .setAttributes(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO)
+                ).build()
+        MockImeSession.create(context).use { session ->
+            MockSpellCheckerClient.create(context, configuration).use {
+                val (_, editText) = startTestActivity()
+                CtsTouchUtils.emulateTapOnViewCenter(
+                        InstrumentationRegistry.getInstrumentation(), null, editText)
+                TestUtils.waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+                session.callCommitText("match", 1)
+                session.callCommitText(" ", 1)
+                TestUtils.waitOnMainUntil({
+                    getSuggestionSpans(editText).find {
+                        (it.flags and SuggestionSpan.FLAG_MISSPELLED) != 0
+                    } != null
+                }, TIMEOUT)
+            }
+        }
+    }
+
+    @Test
+    fun grammarError() {
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .addSuggestionRules(
+                        MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                .setMatch("match")
+                                .addSuggestions("suggestion")
+                                .setAttributes(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR)
+        ).build()
+        MockImeSession.create(context).use { session ->
+            MockSpellCheckerClient.create(context, configuration).use {
+                val (_, editText) = startTestActivity()
+                CtsTouchUtils.emulateTapOnViewCenter(
+                        InstrumentationRegistry.getInstrumentation(), null, editText)
+                TestUtils.waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+                session.callCommitText("match", 1)
+                session.callCommitText(" ", 1)
+                TestUtils.waitOnMainUntil({
+                    getSuggestionSpans(editText).find {
+                        (it.flags and SuggestionSpan.FLAG_GRAMMAR_ERROR) != 0
+                    } != null
+                }, TIMEOUT)
+            }
+        }
+    }
+
+    private fun getSuggestionSpans(editText: EditText): Array<SuggestionSpan> {
+        val editable = editText.text
+        val spans = editable.getSpans(0, editable.length, SuggestionSpan::class.java)
+        return spans
+    }
+
+    private fun startTestActivity(): Pair<TestActivity, EditText> {
+        var editText: EditText? = null
+        val activity = TestActivity.startSync { activity: TestActivity? ->
+            val layout = LinearLayout(activity)
+            editText = EditText(activity)
+            layout.addView(editText, LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT))
+            layout
+        }
+        return Pair(activity, editText!!)
+    }
+}
\ No newline at end of file
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
index 04ac957..0b44a24 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
@@ -137,6 +137,19 @@
                 .getInstrumentation().startActivitySync(intent);
     }
 
+    public static TestActivity startNewTaskSync(
+            @NonNull Function<TestActivity, View> activityInitializer) {
+        sInitializer.set(activityInitializer);
+        final Intent intent = new Intent()
+                .setAction(Intent.ACTION_MAIN)
+                .setClass(InstrumentationRegistry.getInstrumentation().getContext(),
+                        TestActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+        return (TestActivity) InstrumentationRegistry
+                .getInstrumentation().startActivitySync(intent);
+    }
+
     /**
      * Updates {@link WindowManager.LayoutParams#softInputMode}.
      *
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestWebView.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestWebView.java
index a2fa830..3ec6f91 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestWebView.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestWebView.java
@@ -23,6 +23,8 @@
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.widget.EditText;
@@ -45,10 +47,23 @@
         private static final String MY_HTML =
                 "<html><body>Editor: <input type='text' name='testInput'></body></html>";
         private UiDevice mUiDevice;
+        private final String mMaker;
 
-        Impl(Context context, UiDevice uiDevice) {
+        Impl(Context context, UiDevice uiDevice, String maker) {
             super(context);
             mUiDevice = uiDevice;
+            mMaker = maker;
+        }
+
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            final InputConnection original = super.onCreateInputConnection(outAttrs);
+            final int inputType = outAttrs.inputType;
+            if ((inputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT
+                    && (inputType & EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) != 0) {
+                outAttrs.privateImeOptions = mMaker;
+            }
+            return original;
         }
 
         private void loadEditorPage() {
@@ -86,7 +101,7 @@
     private TestWebView() {
     }
 
-    public static UiObject2 launchTestWebViewActivity(long timeoutMs)
+    public static UiObject2 launchTestWebViewActivity(long timeoutMs, String maker)
             throws Exception {
         final AtomicReference<UiObject2> inputTextFieldRef = new AtomicReference<>();
         final AtomicReference<TestWebView.Impl> webViewRef = new AtomicReference<>();
@@ -97,7 +112,7 @@
         TestActivity.startSync(activity -> {
             final LinearLayout layout = new LinearLayout(activity);
             final TestWebView.Impl webView = new Impl(activity, UiDevice.getInstance(
-                    InstrumentationRegistry.getInstrumentation()));
+                    InstrumentationRegistry.getInstrumentation()), maker);
             webView.setWebViewClient(new WebViewClient() {
                 @Override
                 public void onPageFinished(WebView view, String url) {
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
index d21b1c1..b6ee390 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
@@ -48,6 +48,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -56,10 +58,12 @@
  */
 public class WindowFocusHandleService extends Service {
     private @Nullable static WindowFocusHandleService sInstance = null;
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
     private static final String TAG = WindowFocusHandleService.class.getSimpleName();
 
     private EditText mPopupTextView;
     private Handler mThreadHandler;
+    private CountDownLatch mUiThreadSignal;
 
     @Override
     public void onCreate() {
@@ -99,6 +103,18 @@
                             + ", hasWindowfocus: " + hasWindowFocus);
                 }
             }
+
+            @Override
+            public boolean onCheckIsTextEditor() {
+                super.onCheckIsTextEditor();
+                if (getHandler() != null && mUiThreadSignal != null) {
+                    if (Thread.currentThread().getId()
+                            == getHandler().getLooper().getThread().getId()) {
+                        mUiThreadSignal.countDown();
+                    }
+                }
+                return true;
+            }
         };
         editText.setOnFocusChangeListener((v, hasFocus) -> {
             if (v == editText) {
@@ -147,8 +163,12 @@
     }
 
     @AnyThread
-    public EditText getPopupTextView(@Nullable AtomicBoolean outPopupTextHasWindowFocusRef) {
+    public EditText getPopupTextView(
+            @Nullable AtomicBoolean outPopupTextHasWindowFocusRef) throws Exception {
         if (outPopupTextHasWindowFocusRef != null) {
+            TestUtils.waitOnMainUntil(() -> mPopupTextView != null,
+                    TIMEOUT, "PopupTextView should be created");
+
             mPopupTextView.post(() -> {
                 final ViewTreeObserver observerForPopupTextView =
                         mPopupTextView.getViewTreeObserver();
@@ -159,6 +179,17 @@
         return mPopupTextView;
     }
 
+    /**
+     * Tests can set a {@link CountDownLatch} to wait until associated action performed on
+     * UI thread.
+     *
+     * @param uiThreadSignal the {@link CountDownLatch} used to countdown.
+     */
+    @AnyThread
+    public void setUiThreadSignal(CountDownLatch uiThreadSignal) {
+        mUiThreadSignal = uiThreadSignal;
+    }
+
     @MainThread
     public void handleReset() {
         if (mPopupTextView != null) {
diff --git a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
index 58d5c42..b0930ec 100644
--- a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
+++ b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
@@ -15,13 +15,25 @@
  */
 package android.view.inputmethod.ctstestapp;
 
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 
 import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Gravity;
 import android.widget.EditText;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 
@@ -32,34 +44,93 @@
 
     private static final String EXTRA_KEY_PRIVATE_IME_OPTIONS =
             "android.view.inputmethod.ctstestapp.EXTRA_KEY_PRIVATE_IME_OPTIONS";
+    private static final String EXTRA_KEY_SHOW_DIALOG =
+            "android.view.inputmethod.ctstestapp.EXTRA_KEY_SHOW_DIALOG";
+
+    private static final String EXTRA_DISMISS_DIALOG = "extra_dismiss_dialog";
+
+    private static final String ACTION_TRIGGER = "broadcast_action_trigger";
+    private AlertDialog mDialog;
+    private final Handler mHandler = new Handler(Looper.myLooper());
+
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getBooleanExtra(EXTRA_DISMISS_DIALOG, false)) {
+                mDialog.dismiss();
+                mHandler.postDelayed(() -> finish(), 100);
+            }
+        }
+    };
 
     @Nullable
-    private String getPrivateImeOptions() {
+    private String getStringIntentExtra(String key) {
         if (getPackageManager().isInstantApp()) {
             final Uri uri = getIntent().getData();
             if (uri == null || !uri.isHierarchical()) {
                 return null;
             }
-            return uri.getQueryParameter(EXTRA_KEY_PRIVATE_IME_OPTIONS);
+            return uri.getQueryParameter(key);
         }
-        return getIntent().getStringExtra(EXTRA_KEY_PRIVATE_IME_OPTIONS);
+        return getIntent().getStringExtra(key);
+    }
+
+    private boolean getBooleanIntentExtra(String key) {
+        if (getPackageManager().isInstantApp()) {
+            final Uri uri = getIntent().getData();
+            if (uri == null || !uri.isHierarchical()) {
+                return false;
+            }
+            return uri.getBooleanQueryParameter(key, false);
+        }
+        return getIntent().getBooleanExtra(key, false);
     }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_TRIGGER));
 
         final LinearLayout layout = new LinearLayout(this);
         layout.setOrientation(LinearLayout.VERTICAL);
-        final EditText editText = new EditText(this);
-        editText.setHint("editText");
-        final String privateImeOptions = getPrivateImeOptions();
-        if (privateImeOptions != null) {
-            editText.setPrivateImeOptions(privateImeOptions);
+        final boolean needShowDialog = getBooleanIntentExtra(EXTRA_KEY_SHOW_DIALOG);
+
+        if (needShowDialog) {
+            layout.setOrientation(LinearLayout.VERTICAL);
+            layout.setGravity(Gravity.BOTTOM);
+            getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
+
+            final TextView textView = new TextView(this);
+            textView.setText("This is DialogActivity");
+            layout.addView(textView);
+
+            mDialog= new AlertDialog.Builder(this)
+                    .setView(new LinearLayout(this))
+                    .create();
+            mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
+            mDialog.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_PAN);
+            mDialog.show();
+        } else {
+            final EditText editText = new EditText(this);
+            editText.setHint("editText");
+            final String privateImeOptions = getStringIntentExtra(EXTRA_KEY_PRIVATE_IME_OPTIONS);
+            if (privateImeOptions != null) {
+                editText.setPrivateImeOptions(privateImeOptions);
+            }
+            editText.requestFocus();
+            layout.addView(editText);
+            getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
         }
-        editText.requestFocus();
-        layout.addView(editText);
-        getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
         setContentView(layout);
     }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mBroadcastReceiver != null) {
+            unregisterReceiver(mBroadcastReceiver);
+            mBroadcastReceiver = null;
+        }
+    }
 }
diff --git a/tests/leanbackjank/TEST_MAPPING b/tests/leanbackjank/TEST_MAPPING
new file mode 100644
index 0000000..64160c8
--- /dev/null
+++ b/tests/leanbackjank/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLeanbackJankTestCases"
+    }
+  ]
+}
diff --git a/tests/leanbackjank/app/AndroidManifest.xml b/tests/leanbackjank/app/AndroidManifest.xml
index 815b8cd..07f2bce 100644
--- a/tests/leanbackjank/app/AndroidManifest.xml
+++ b/tests/leanbackjank/app/AndroidManifest.xml
@@ -16,44 +16,41 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="android.leanbackjank.app"
-    android:versionCode="1"
-    android:versionName="1.1" >
+     xmlns:tools="http://schemas.android.com/tools"
+     package="android.leanbackjank.app"
+     android:versionCode="1"
+     android:versionName="1.1">
 
-    <uses-sdk
-        android:minSdkVersion="21"
-        android:targetSdkVersion="23" />
+    <uses-sdk android:minSdkVersion="21"
+         android:targetSdkVersion="23"/>
 
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
 
-    <uses-feature
-        android:name="android.hardware.touchscreen"
-        android:required="false" />
+    <uses-feature android:name="android.hardware.touchscreen"
+         android:required="false"/>
 
     <uses-feature android:name="android.software.leanback"
-        android:required="true" />
+         android:required="true"/>
 
-    <application
-        android:allowBackup="false"
-        android:icon="@drawable/videos_by_google_banner"
-        android:label="@string/app_name"
-        android:logo="@drawable/videos_by_google_banner"
-        android:theme="@style/Theme.Example.Leanback"
-        tools:replace="android:appComponentFactory"
-        android:appComponentFactory="android.support.v4.app.CoreComponentFactory" >
-        <uses-library android:name="android.test.runner" />
+    <application android:allowBackup="false"
+         android:icon="@drawable/videos_by_google_banner"
+         android:label="@string/app_name"
+         android:logo="@drawable/videos_by_google_banner"
+         android:theme="@style/Theme.Example.Leanback"
+         tools:replace="android:appComponentFactory"
+         android:appComponentFactory="android.support.v4.app.CoreComponentFactory">
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name=".ui.MainActivity"
-            android:icon="@drawable/videos_by_google_banner"
-            android:label="@string/app_name"
-            android:logo="@drawable/videos_by_google_banner"
-            android:screenOrientation="landscape" >
+        <activity android:name=".ui.MainActivity"
+             android:icon="@drawable/videos_by_google_banner"
+             android:label="@string/app_name"
+             android:logo="@drawable/videos_by_google_banner"
+             android:screenOrientation="landscape"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/libcore/jsr166/TEST_MAPPING b/tests/libcore/jsr166/TEST_MAPPING
new file mode 100644
index 0000000..7657d68
--- /dev/null
+++ b/tests/libcore/jsr166/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLibcoreJsr166TestCases"
+    }
+  ]
+}
diff --git a/tests/libcore/wycheproof/TEST_MAPPING b/tests/libcore/wycheproof/TEST_MAPPING
new file mode 100644
index 0000000..7993ad6
--- /dev/null
+++ b/tests/libcore/wycheproof/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLibcoreWycheproofConscryptTestCases"
+    }
+  ]
+}
diff --git a/tests/location/OWNERS b/tests/location/OWNERS
index 61eb794..90fdb22 100644
--- a/tests/location/OWNERS
+++ b/tests/location/OWNERS
@@ -1,7 +1,7 @@
 # Bug component: 32850
+sooniln@google.com
 dnchrist@google.com
 sashakuznetsov@google.com
-sooniln@google.com
 weiwa@google.com
 wyattriley@google.com
 yuhany@google.com
diff --git a/tests/location/common/src/android/location/cts/common/ProximityPendingIntentCapture.java b/tests/location/common/src/android/location/cts/common/ProximityPendingIntentCapture.java
index 75e4e39..9966c41 100644
--- a/tests/location/common/src/android/location/cts/common/ProximityPendingIntentCapture.java
+++ b/tests/location/common/src/android/location/cts/common/ProximityPendingIntentCapture.java
@@ -28,7 +28,9 @@
 
         mLocationManager = context.getSystemService(LocationManager.class);
         mPendingIntent = PendingIntent.getBroadcast(context, sRequestCode.getAndIncrement(),
-                new Intent(ACTION).setPackage(context.getPackageName()),
+                new Intent(ACTION)
+                        .setPackage(context.getPackageName())
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
                 PendingIntent.FLAG_CANCEL_CURRENT);
         mProximityChanges = new LinkedBlockingQueue<>();
 
diff --git a/tests/location/common/src/android/location/cts/common/TestLocationManager.java b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
index bed3793..e9b598b 100644
--- a/tests/location/common/src/android/location/cts/common/TestLocationManager.java
+++ b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
@@ -117,15 +117,13 @@
      */
     public void requestLowPowerModeGnssLocationUpdates(int minTimeMillis,
             LocationListener locationListener) {
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
-                LocationManager.GPS_PROVIDER, /* minTime= */ minTimeMillis, /* minDistance= */0,
-                false);
-        request.setLowPowerMode(true);
         if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
             Log.i(TAG, "Request Location updates.");
-            mLocationManager.requestLocationUpdates(request,
-                    locationListener,
-                    Looper.getMainLooper());
+            mLocationManager.requestLocationUpdates(
+                    LocationManager.GPS_PROVIDER,
+                    new LocationRequest.Builder(minTimeMillis).setLowPower(true).build(),
+                    mContext.getMainExecutor(),
+                    locationListener);
         }
     }
 
diff --git a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
index 05b80b9..adc8da4 100644
--- a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
+++ b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
@@ -19,11 +19,14 @@
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationManager.PASSIVE_PROVIDER;
+import static android.provider.Settings.Secure.LOCATION_COARSE_ACCURACY_M;
 
 import static androidx.test.ext.truth.location.LocationSubject.assertThat;
 
 import static com.android.compatibility.common.util.LocationUtils.createLocation;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -38,9 +41,12 @@
 import android.location.LocationManager;
 import android.location.cts.common.LocationListenerCapture;
 import android.location.cts.common.LocationPendingIntentCapture;
+import android.location.cts.common.ProximityPendingIntentCapture;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
 import android.util.Log;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -56,7 +62,6 @@
 
 import java.util.List;
 import java.util.Random;
-import java.util.concurrent.Executor;
 
 @RunWith(AndroidJUnit4.class)
 public class LocationManagerCoarseTest {
@@ -65,8 +70,7 @@
 
     private static final long TIMEOUT_MS = 5000;
 
-    // 2000m is the default grid size used by location fudger
-    private static final float MAX_COARSE_FUDGE_DISTANCE_M = 2500f;
+    private static final float MIN_COARSE_FUDGE_DISTANCE_M = 2000f;
 
     private static final String TEST_PROVIDER = "test_provider";
 
@@ -74,6 +78,8 @@
     private Context mContext;
     private LocationManager mManager;
 
+    private float mMaxCoarseFudgeDistanceM;
+
     @Before
     public void setUp() throws Exception {
         LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
@@ -86,6 +92,13 @@
         mContext = ApplicationProvider.getApplicationContext();
         mManager = mContext.getSystemService(LocationManager.class);
 
+        float coarseLocationAccuracyM = Settings.Secure.getFloat(
+                mContext.getContentResolver(),
+                LOCATION_COARSE_ACCURACY_M,
+                MIN_COARSE_FUDGE_DISTANCE_M);
+        mMaxCoarseFudgeDistanceM = (float) Math.sqrt(
+                2 * coarseLocationAccuracyM * coarseLocationAccuracyM);
+
         assertNotNull(mManager);
 
         for (String provider : mManager.getAllProviders()) {
@@ -116,11 +129,20 @@
     }
 
     @Test
+    public void testMinCoarseLocationDistance() {
+        assertThat(Settings.Secure.getFloat(
+                mContext.getContentResolver(),
+                LOCATION_COARSE_ACCURACY_M,
+                MIN_COARSE_FUDGE_DISTANCE_M)).isAtLeast(MIN_COARSE_FUDGE_DISTANCE_M);
+    }
+
+    @Test
     public void testGetLastKnownLocation() {
         Location loc = createLocation(TEST_PROVIDER, mRandom);
 
         mManager.setTestProviderLocation(TEST_PROVIDER, loc);
-        assertThat(mManager.getLastKnownLocation(TEST_PROVIDER)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+        assertThat(mManager.getLastKnownLocation(TEST_PROVIDER)).isNearby(loc,
+                mMaxCoarseFudgeDistanceM);
     }
 
     @Test
@@ -137,28 +159,32 @@
     @Test
     public void testRequestLocationUpdates() throws Exception {
         Location loc = createLocation(TEST_PROVIDER, mRandom);
-        Bundle extras = new Bundle();
-        extras.putParcelable(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
-        loc.setExtras(extras);
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
+            Bundle extras = new Bundle();
+            extras.putParcelable(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
+            loc.setExtras(extras);
+        }
 
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, directExecutor(), capture);
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, Runnable::run, capture);
             mManager.setTestProviderLocation(TEST_PROVIDER, loc);
-            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, mMaxCoarseFudgeDistanceM);
         }
     }
 
     @Test
     public void testRequestLocationUpdates_PendingIntent() throws Exception {
         Location loc = createLocation(TEST_PROVIDER, mRandom);
-        Bundle extras = new Bundle();
-        extras.putParcelable(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
-        loc.setExtras(extras);
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
+            Bundle extras = new Bundle();
+            extras.putParcelable(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
+            loc.setExtras(extras);
+        }
 
         try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
             mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, capture.getPendingIntent());
             mManager.setTestProviderLocation(TEST_PROVIDER, loc);
-            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, mMaxCoarseFudgeDistanceM);
         }
     }
 
@@ -206,6 +232,18 @@
         mManager.sendExtraCommand(TEST_PROVIDER, "command", null);
     }
 
+    @Test
+    public void testAddProximityAlert() {
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            try {
+                mManager.addProximityAlert(0, 0, 100, -1, capture.getPendingIntent());
+                fail("addProximityAlert() should fail with only ACCESS_COARSE_LOCATION");
+            } catch (SecurityException e) {
+                // pass
+            }
+        }
+    }
+
     // TODO: this test should probably not be in the location module
     @Test
     public void testGnssProvidedClock() throws Exception {
@@ -240,10 +278,6 @@
         assertTrue(System.currentTimeMillis() - clockms < 1000);
     }
 
-    private static Executor directExecutor() {
-        return Runnable::run;
-    }
-
     private boolean hasGpsFeature() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS);
     }
diff --git a/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java b/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java
deleted file mode 100644
index a17755a..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.fine;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.util.Locale;
-
-import android.location.Address;
-import android.os.Bundle;
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class AddressTest {
-
-    private static final double DELTA = 0.001;
-
-    @Test
-    public void testConstructor() {
-        new Address(Locale.ENGLISH);
-
-        new Address(Locale.FRANCE);
-
-        new Address(null);
-    }
-
-    @Test
-    public void testAccessAdminArea() {
-        Address address = new Address(Locale.ITALY);
-
-        String adminArea = "CA";
-        address.setAdminArea(adminArea);
-        assertEquals(adminArea, address.getAdminArea());
-
-        address.setAdminArea(null);
-        assertNull(address.getAdminArea());
-    }
-
-    @Test
-    public void testAccessCountryCode() {
-        Address address = new Address(Locale.JAPAN);
-
-        String countryCode = "US";
-        address.setCountryCode(countryCode);
-        assertEquals(countryCode, address.getCountryCode());
-
-        address.setCountryCode(null);
-        assertNull(address.getCountryCode());
-    }
-
-    @Test
-    public void testAccessCountryName() {
-        Address address = new Address(Locale.KOREA);
-
-        String countryName = "China";
-        address.setCountryName(countryName);
-        assertEquals(countryName, address.getCountryName());
-
-        address.setCountryName(null);
-        assertNull(address.getCountryName());
-    }
-
-    @Test
-    public void testAccessExtras() {
-        Address address = new Address(Locale.TAIWAN);
-
-        Bundle extras = new Bundle();
-        extras.putBoolean("key1", false);
-        byte b = 10;
-        extras.putByte("key2", b);
-
-        address.setExtras(extras);
-        Bundle actual = address.getExtras();
-        assertFalse(actual.getBoolean("key1"));
-        assertEquals(b, actual.getByte("key2"));
-
-        address.setExtras(null);
-        assertNull(address.getExtras());
-    }
-
-    @Test
-    public void testAccessFeatureName() {
-        Address address = new Address(Locale.SIMPLIFIED_CHINESE);
-
-        String featureName = "Golden Gate Bridge";
-        address.setFeatureName(featureName);
-        assertEquals(featureName, address.getFeatureName());
-
-        address.setFeatureName(null);
-        assertNull(address.getFeatureName());
-    }
-
-    @Test
-    public void testAccessLatitude() {
-        Address address = new Address(Locale.CHINA);
-        assertFalse(address.hasLatitude());
-
-        double latitude = 1.23456789;
-        address.setLatitude(latitude);
-        assertTrue(address.hasLatitude());
-        assertEquals(latitude, address.getLatitude(), DELTA);
-
-        address.clearLatitude();
-        assertFalse(address.hasLatitude());
-        try {
-            address.getLatitude();
-            fail("should throw IllegalStateException.");
-        } catch (IllegalStateException e) {
-            // pass
-        }
-    }
-
-    @Test
-    public void testAccessLongitude() {
-        Address address = new Address(Locale.CHINA);
-        assertFalse(address.hasLongitude());
-
-        double longitude = 1.23456789;
-        address.setLongitude(longitude);
-        assertTrue(address.hasLongitude());
-        assertEquals(longitude, address.getLongitude(), DELTA);
-
-        address.clearLongitude();
-        assertFalse(address.hasLongitude());
-        try {
-            address.getLongitude();
-            fail("should throw IllegalStateException.");
-        } catch (IllegalStateException e) {
-            // pass
-        }
-    }
-
-    @Test
-    public void testAccessPhone() {
-        Address address = new Address(Locale.CHINA);
-
-        String phone = "+86-13512345678";
-        address.setPhone(phone);
-        assertEquals(phone, address.getPhone());
-
-        address.setPhone(null);
-        assertNull(address.getPhone());
-    }
-
-    @Test
-    public void testAccessPostalCode() {
-        Address address = new Address(Locale.CHINA);
-
-        String postalCode = "93110";
-        address.setPostalCode(postalCode);
-        assertEquals(postalCode, address.getPostalCode());
-
-        address.setPostalCode(null);
-        assertNull(address.getPostalCode());
-    }
-
-    @Test
-    public void testAccessThoroughfare() {
-        Address address = new Address(Locale.CHINA);
-
-        String thoroughfare = "1600 Ampitheater Parkway";
-        address.setThoroughfare(thoroughfare);
-        assertEquals(thoroughfare, address.getThoroughfare());
-
-        address.setThoroughfare(null);
-        assertNull(address.getThoroughfare());
-    }
-
-    @Test
-    public void testAccessUrl() {
-        Address address = new Address(Locale.CHINA);
-
-        String Url = "Url";
-        address.setUrl(Url);
-        assertEquals(Url, address.getUrl());
-
-        address.setUrl(null);
-        assertNull(address.getUrl());
-    }
-
-    @Test
-    public void testAccessSubAdminArea() {
-        Address address = new Address(Locale.CHINA);
-
-        String subAdminArea = "Santa Clara County";
-        address.setSubAdminArea(subAdminArea);
-        assertEquals(subAdminArea, address.getSubAdminArea());
-
-        address.setSubAdminArea(null);
-        assertNull(address.getSubAdminArea());
-    }
-
-    @Test
-    public void testToString() {
-        Address address = new Address(Locale.CHINA);
-
-        address.setUrl("www.google.com");
-        address.setPostalCode("95120");
-        String expected = "Address[addressLines=[],feature=null,admin=null,sub-admin=null," +
-                "locality=null,thoroughfare=null,postalCode=95120,countryCode=null," +
-                "countryName=null,hasLatitude=false,latitude=0.0,hasLongitude=false," +
-                "longitude=0.0,phone=null,url=www.google.com,extras=null]";
-        assertEquals(expected, address.toString());
-    }
-
-    @Test
-    public void testAddressLine() {
-        Address address = new Address(Locale.CHINA);
-
-        try {
-            address.setAddressLine(-1, null);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-
-        try {
-            address.getAddressLine(-1);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-
-        address.setAddressLine(0, null);
-        assertNull(address.getAddressLine(0));
-        assertEquals(0, address.getMaxAddressLineIndex());
-
-        final String line1 = "1";
-        address.setAddressLine(0, line1);
-        assertEquals(line1, address.getAddressLine(0));
-        assertEquals(0, address.getMaxAddressLineIndex());
-
-        final String line2 = "2";
-        address.setAddressLine(5, line2);
-        assertEquals(line2, address.getAddressLine(5));
-        assertEquals(5, address.getMaxAddressLineIndex());
-
-        address.setAddressLine(2, null);
-        assertNull(address.getAddressLine(2));
-        assertEquals(5, address.getMaxAddressLineIndex());
-    }
-
-    @Test
-    public void testGetLocale() {
-        Locale locale = Locale.US;
-        Address address = new Address(locale);
-        assertSame(locale, address.getLocale());
-
-        locale = Locale.UK;
-        address = new Address(locale);
-        assertSame(locale, address.getLocale());
-
-        address = new Address(null);
-        assertNull(address.getLocale());
-    }
-
-    @Test
-    public void testAccessLocality() {
-        Address address = new Address(Locale.PRC);
-
-        String locality = "Hollywood";
-        address.setLocality(locality);
-        assertEquals(locality, address.getLocality());
-
-        address.setLocality(null);
-        assertNull(address.getLocality());
-    }
-
-    @Test
-    public void testAccessPremises() {
-        Address address = new Address(Locale.PRC);
-
-        String premises = "Appartment";
-        address.setPremises(premises);
-        assertEquals(premises, address.getPremises());
-
-        address.setPremises(null);
-        assertNull(address.getPremises());
-    }
-
-    @Test
-    public void testAccessSubLocality() {
-        Address address = new Address(Locale.PRC);
-
-        String subLocality = "Sarchnar";
-        address.setSubLocality(subLocality);
-        assertEquals(subLocality, address.getSubLocality());
-
-        address.setSubLocality(null);
-        assertNull(address.getSubLocality());
-    }
-
-    @Test
-    public void testAccessSubThoroughfare() {
-        Address address = new Address(Locale.PRC);
-
-        String subThoroughfare = "1600";
-        address.setSubThoroughfare(subThoroughfare);
-        assertEquals(subThoroughfare, address.getSubThoroughfare());
-
-        address.setSubThoroughfare(null);
-        assertNull(address.getSubThoroughfare());
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        Locale locale = Locale.KOREA;
-        Address address = new Address(locale);
-
-        Parcel parcel = Parcel.obtain();
-        address.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        assertEquals(locale.getLanguage(), parcel.readString());
-        assertEquals(locale.getCountry(), parcel.readString());
-        assertEquals(0, parcel.readInt());
-        assertEquals(address.getFeatureName(), parcel.readString());
-        assertEquals(address.getAdminArea(), parcel.readString());
-        assertEquals(address.getSubAdminArea(), parcel.readString());
-        assertEquals(address.getLocality(), parcel.readString());
-        assertEquals(address.getSubLocality(), parcel.readString());
-        assertEquals(address.getThoroughfare(), parcel.readString());
-        assertEquals(address.getSubThoroughfare(), parcel.readString());
-        assertEquals(address.getPremises(), parcel.readString());
-        assertEquals(address.getPostalCode(), parcel.readString());
-        assertEquals(address.getCountryCode(), parcel.readString());
-        assertEquals(address.getCountryName(), parcel.readString());
-        assertEquals(0, parcel.readInt());
-        assertEquals(0, parcel.readInt());
-        assertEquals(address.getPhone(), parcel.readString());
-        assertEquals(address.getUrl(), parcel.readString());
-        assertEquals(address.getExtras(), parcel.readBundle());
-
-        parcel.recycle();
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java b/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java
deleted file mode 100644
index 0799636..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.fine;
-
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.location.Criteria;
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class CriteriaTest {
-
-    @Test
-    public void testConstructor() {
-        new Criteria();
-
-        Criteria c = new Criteria();
-        c.setAccuracy(Criteria.ACCURACY_FINE);
-        c.setAltitudeRequired(true);
-        c.setBearingRequired(true);
-        c.setCostAllowed(true);
-        c.setPowerRequirement(Criteria.POWER_HIGH);
-        c.setSpeedRequired(true);
-        Criteria criteria = new Criteria(c);
-        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
-        assertTrue(criteria.isAltitudeRequired());
-        assertTrue(criteria.isBearingRequired());
-        assertTrue(criteria.isCostAllowed());
-        assertTrue(criteria.isSpeedRequired());
-        assertEquals(Criteria.POWER_HIGH, criteria.getPowerRequirement());
-
-        try {
-            new Criteria(null);
-            fail("should throw NullPointerException.");
-        } catch (NullPointerException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testDescribeContents() {
-        Criteria criteria = new Criteria();
-        criteria.describeContents();
-    }
-
-    @Test
-    public void testAccessAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setAccuracy(Criteria.ACCURACY_FINE);
-        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
-
-        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
-        assertEquals(Criteria.ACCURACY_COARSE, criteria.getAccuracy());
-
-        try {
-            // It should throw IllegalArgumentException
-            criteria.setAccuracy(-1);
-            // issue 1728526
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            // It should throw IllegalArgumentException
-            criteria.setAccuracy(Criteria.ACCURACY_COARSE + 1);
-            // issue 1728526
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testAccessPowerRequirement() {
-        Criteria criteria = new Criteria();
-
-        criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getPowerRequirement());
-
-        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
-        assertEquals(Criteria.POWER_MEDIUM, criteria.getPowerRequirement());
-
-        try {
-            criteria.setPowerRequirement(-1);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            criteria.setPowerRequirement(Criteria.POWER_HIGH + 1);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testAccessAltitudeRequired() {
-        Criteria criteria = new Criteria();
-
-        criteria.setAltitudeRequired(false);
-        assertFalse(criteria.isAltitudeRequired());
-
-        criteria.setAltitudeRequired(true);
-        assertTrue(criteria.isAltitudeRequired());
-    }
-
-    @Test
-    public void testAccessBearingAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setBearingAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getBearingAccuracy());
-
-        criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getBearingAccuracy());
-
-        criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getBearingAccuracy());
-      }
-
-    @Test
-    public void testAccessBearingRequired() {
-        Criteria criteria = new Criteria();
-
-        criteria.setBearingRequired(false);
-        assertFalse(criteria.isBearingRequired());
-
-        criteria.setBearingRequired(true);
-        assertTrue(criteria.isBearingRequired());
-    }
-
-    @Test
-    public void testAccessCostAllowed() {
-        Criteria criteria = new Criteria();
-
-        criteria.setCostAllowed(false);
-        assertFalse(criteria.isCostAllowed());
-
-        criteria.setCostAllowed(true);
-        assertTrue(criteria.isCostAllowed());
-    }
-
-    @Test
-    public void testAccessHorizontalAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getHorizontalAccuracy());
-
-        criteria.setHorizontalAccuracy(Criteria.ACCURACY_MEDIUM);
-        assertEquals(Criteria.ACCURACY_MEDIUM, criteria.getHorizontalAccuracy());
-
-        criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getHorizontalAccuracy());
-
-        criteria.setHorizontalAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getHorizontalAccuracy());
-    }
-
-    @Test
-    public void testAccessSpeedAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setSpeedAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getSpeedAccuracy());
-
-        criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getSpeedAccuracy());
-
-        criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getSpeedAccuracy());
-    }
-
-    @Test
-    public void testAccessSpeedRequired() {
-        Criteria criteria = new Criteria();
-
-        criteria.setSpeedRequired(false);
-        assertFalse(criteria.isSpeedRequired());
-
-        criteria.setSpeedRequired(true);
-        assertTrue(criteria.isSpeedRequired());
-    }
-
-    @Test
-    public void testAccessVerticalAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setVerticalAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getVerticalAccuracy());
-
-       criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getVerticalAccuracy());
-
-        criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getVerticalAccuracy());
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        Criteria criteria = new Criteria();
-        criteria.setAltitudeRequired(true);
-        criteria.setBearingRequired(false);
-        criteria.setCostAllowed(true);
-        criteria.setSpeedRequired(true);
-
-        Parcel parcel = Parcel.obtain();
-        criteria.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        Criteria newCriteria = Criteria.CREATOR.createFromParcel(parcel);
-
-        assertEquals(criteria.getAccuracy(), newCriteria.getAccuracy());
-        assertEquals(criteria.getPowerRequirement(), newCriteria.getPowerRequirement());
-        assertEquals(criteria.isAltitudeRequired(), newCriteria.isAltitudeRequired());
-        assertEquals(criteria.isBearingRequired(), newCriteria.isBearingRequired());
-        assertEquals(criteria.isSpeedRequired(), newCriteria.isSpeedRequired());
-        assertEquals(criteria.isCostAllowed(), newCriteria.isCostAllowed());
-
-        parcel.recycle();
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GeofencingTest.java b/tests/location/location_fine/src/android/location/cts/fine/GeofencingTest.java
new file mode 100644
index 0000000..92580c9
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/GeofencingTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.fine;
+
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.PROVIDERS_CHANGED_ACTION;
+
+import static com.android.compatibility.common.util.LocationUtils.createLocation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.location.Criteria;
+import android.location.LocationManager;
+import android.location.cts.common.ProximityPendingIntentCapture;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.LocationUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Objects;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class GeofencingTest {
+
+    private static final String TAG = "GeofenceManagerTest";
+
+    private static final long TIMEOUT_MS = 5000;
+    private static final long FAILURE_TIMEOUT_MS = 200;
+
+    private static final String TEST_PROVIDER = "test_provider";
+
+    private Context mContext;
+    private LocationManager mManager;
+
+    @Before
+    public void setUp() throws Exception {
+        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+                true);
+
+        long seed = System.currentTimeMillis();
+        Log.i(TAG, "location random seed: " + seed);
+
+        mContext = ApplicationProvider.getApplicationContext();
+        mManager = Objects.requireNonNull(mContext.getSystemService(LocationManager.class));
+
+        for (String provider : mManager.getAllProviders()) {
+            mManager.removeTestProvider(provider);
+        }
+
+        mManager.addTestProvider(TEST_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mManager != null) {
+            for (String provider : mManager.getAllProviders()) {
+                mManager.removeTestProvider(provider);
+            }
+        }
+
+        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+                false);
+    }
+
+    @Test
+    public void testAddProximityAlert() throws Exception {
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER,
+                createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER,
+                    createLocation(FUSED_PROVIDER, 0, 0, 10));
+            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER,
+                    createLocation(FUSED_PROVIDER, 30, 30, 10));
+            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+        }
+
+        try {
+            mManager.addProximityAlert(0, 0, 1000, -1, null);
+            fail("Should throw IllegalArgumentException if pending intent is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            try {
+                mManager.addProximityAlert(0, 0, 0, -1, capture.getPendingIntent());
+                fail("Should throw IllegalArgumentException if radius == 0!");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+
+            try {
+                mManager.addProximityAlert(0, 0, -1, -1, capture.getPendingIntent());
+                fail("Should throw IllegalArgumentException if radius < 0!");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+
+            try {
+                mManager.addProximityAlert(1000, 1000, 1000, -1, capture.getPendingIntent());
+                fail("Should throw IllegalArgumentException if lat/lon are illegal!");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveProximityAlert() throws Exception {
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER,
+                createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+            mManager.removeProximityAlert(capture.getPendingIntent());
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER,
+                    createLocation(FUSED_PROVIDER, 0, 0, 10));
+            assertThat(capture.getNextProximityChange(FAILURE_TIMEOUT_MS)).isNull();
+        }
+
+        try {
+            mManager.removeProximityAlert(null);
+            fail("Should throw IllegalArgumentException if pending intent is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testAddProximityAlert_StartProximate() throws Exception {
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+        }
+    }
+
+    @Test
+    public void testAddProximityAlert_Multiple() throws Exception {
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER,
+                createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+        ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext);
+        try {
+            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+            mManager.addProximityAlert(30, 30, 1000, -1, capture.getPendingIntent());
+
+            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER,
+                    createLocation(FUSED_PROVIDER, 0, 0, 10));
+            Boolean first = capture.getNextProximityChange(TIMEOUT_MS);
+            assertThat(first).isNotNull();
+            Boolean second = capture.getNextProximityChange(TIMEOUT_MS);
+            assertThat(second).isNotNull();
+            assertThat(first).isNotEqualTo(second);
+        } finally {
+            capture.close();
+        }
+
+        mManager.setTestProviderLocation(FUSED_PROVIDER,
+                createLocation(FUSED_PROVIDER, 30, 30, 10));
+        assertThat(capture.getNextProximityChange(FAILURE_TIMEOUT_MS)).isNull();
+    }
+
+    @Test
+    public void testAddProximityAlert_Expires() throws Exception {
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER,
+                createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mManager.addProximityAlert(0, 0, 1000, 1, capture.getPendingIntent());
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER,
+                    createLocation(FUSED_PROVIDER, 0, 0, 10));
+            assertThat(capture.getNextProximityChange(FAILURE_TIMEOUT_MS)).isNull();
+        }
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoTest.java
deleted file mode 100644
index cbaabf7..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoTest.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts.fine;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.location.GnssAntennaInfo;
-import android.location.GnssAntennaInfo.PhaseCenterOffset;
-import android.location.GnssAntennaInfo.SphericalCorrections;
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests fundamental functionality of GnssAntennaInfo class. This includes writing and reading from
- * parcel, and verifying computed values and getters.
- */
-@RunWith(AndroidJUnit4.class)
-public class GnssAntennaInfoTest {
-
-    private static final double PRECISION = 0.0001;
-    private static final double[][] PHASE_CENTER_VARIATION_CORRECTIONS = new double[][]{
-        {5.29, 0.20, 7.15, 10.18, 9.47, 8.05},
-        {11.93, 3.98, 2.68, 2.66, 8.15, 13.54},
-        {14.69, 7.63, 13.46, 8.70, 4.36, 1.21},
-        {4.19, 12.43, 12.40, 0.90, 1.96, 1.99},
-        {7.30, 0.49, 7.43, 8.71, 3.70, 7.24},
-        {4.79, 1.88, 13.88, 3.52, 13.40, 11.81}
-    };
-    private static final double[][] PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES = new double[][]{
-            {1.77, 0.81, 0.72, 1.65, 2.35, 1.22},
-            {0.77, 3.43, 2.77, 0.97, 4.55, 1.38},
-            {1.51, 2.50, 2.23, 2.43, 1.94, 0.90},
-            {0.34, 4.72, 4.14, 4.78, 4.57, 1.69},
-            {4.49, 0.05, 2.78, 1.33, 3.20, 2.75},
-            {1.09, 0.31, 3.79, 4.32, 0.65, 1.23}
-    };
-    private static final double[][] SIGNAL_GAIN_CORRECTIONS = new double[][]{
-            {0.19, 7.04, 1.65, 14.84, 2.95, 9.21},
-            {0.45, 6.27, 14.57, 8.95, 3.92, 12.68},
-            {6.80, 13.04, 7.92, 2.23, 14.22, 7.36},
-            {4.81, 11.78, 5.04, 5.13, 12.09, 12.85},
-            {0.88, 4.04, 5.71, 3.72, 12.62, 0.40},
-            {14.26, 9.50, 4.21, 11.14, 6.54, 14.63}
-    };
-    private static final double[][] SIGNAL_GAIN_CORRECTION_UNCERTAINTIES = new double[][]{
-            {4.74, 1.54, 1.59, 4.05, 1.65, 2.46},
-            {0.10, 0.33, 0.84, 0.83, 0.57, 2.66},
-            {2.08, 1.46, 2.10, 3.25, 1.48, 0.65},
-            {4.02, 2.90, 2.51, 2.13, 1.67, 1.23},
-            {2.13, 4.30, 1.36, 3.86, 1.02, 2.96},
-            {3.22, 3.95, 3.75, 1.73, 1.91, 4.93}
-
-    };
-
-    @Test
-    public void testFullAntennaInfoDescribeContents() {
-        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
-        assertEquals(0, gnssAntennaInfo.describeContents());
-    }
-
-    @Test
-    public void testPartialAntennaInfoDescribeContents() {
-        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
-        assertEquals(0, gnssAntennaInfo.describeContents());
-    }
-
-    @Test
-    public void testFullAntennaInfoWriteToParcel() {
-        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
-        Parcel parcel = Parcel.obtain();
-        gnssAntennaInfo.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssAntennaInfo newGnssAntennaInfo = GnssAntennaInfo.CREATOR.createFromParcel(parcel);
-        verifyFullGnssAntennaInfoValuesAndGetters(newGnssAntennaInfo);
-        parcel.recycle();
-    }
-
-    @Test
-    public void testPartialAntennaInfoWriteToParcel() {
-        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
-        Parcel parcel = Parcel.obtain();
-        gnssAntennaInfo.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssAntennaInfo newGnssAntennaInfo = GnssAntennaInfo.CREATOR.createFromParcel(parcel);
-        verifyPartialGnssAntennaInfoValuesAndGetters(newGnssAntennaInfo);
-        parcel.recycle();
-    }
-
-    @Test
-    public void testCreateFullGnssAntennaInfoAndGetValues() {
-        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
-        verifyFullGnssAntennaInfoValuesAndGetters(gnssAntennaInfo);
-    }
-
-    @Test
-    public void testCreatePartialGnssAntennaInfoAndGetValues() {
-        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
-        verifyPartialGnssAntennaInfoValuesAndGetters(gnssAntennaInfo);
-    }
-
-    private static GnssAntennaInfo createFullTestGnssAntennaInfo() {
-        double carrierFrequencyMHz = 13758.0;
-
-        GnssAntennaInfo.PhaseCenterOffset phaseCenterOffset = new
-                GnssAntennaInfo.PhaseCenterOffset(
-                        4.3d,
-                    1.4d,
-                    2.10d,
-                    2.1d,
-                    3.12d,
-                    0.5d);
-
-        double[][] phaseCenterVariationCorrectionsMillimeters = PHASE_CENTER_VARIATION_CORRECTIONS;
-        double[][] phaseCenterVariationCorrectionsUncertaintyMillimeters =
-                PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES;
-        SphericalCorrections
-                phaseCenterVariationCorrections =
-                new SphericalCorrections(
-                        phaseCenterVariationCorrectionsMillimeters,
-                        phaseCenterVariationCorrectionsUncertaintyMillimeters);
-
-        double[][] signalGainCorrectionsDbi = SIGNAL_GAIN_CORRECTIONS;
-        double[][] signalGainCorrectionsUncertaintyDbi = SIGNAL_GAIN_CORRECTION_UNCERTAINTIES;
-        SphericalCorrections signalGainCorrections = new
-                SphericalCorrections(
-                signalGainCorrectionsDbi,
-                signalGainCorrectionsUncertaintyDbi);
-
-        return new GnssAntennaInfo.Builder()
-                .setCarrierFrequencyMHz(carrierFrequencyMHz)
-                .setPhaseCenterOffset(phaseCenterOffset)
-                .setPhaseCenterVariationCorrections(phaseCenterVariationCorrections)
-                .setSignalGainCorrections(signalGainCorrections)
-                .build();
-    }
-
-    private static GnssAntennaInfo createPartialTestGnssAntennaInfo() {
-        double carrierFrequencyMHz = 13758.0;
-
-        GnssAntennaInfo.PhaseCenterOffset phaseCenterOffset = new
-                GnssAntennaInfo.PhaseCenterOffset(
-                4.3d,
-                1.4d,
-                2.10d,
-                2.1d,
-                3.12d,
-                0.5d);
-
-        return new GnssAntennaInfo.Builder()
-                .setCarrierFrequencyMHz(carrierFrequencyMHz)
-                .setPhaseCenterOffset(phaseCenterOffset)
-                .build();
-    }
-
-    private static void verifyPartialGnssAntennaInfoValuesAndGetters(GnssAntennaInfo gnssAntennaInfo) {
-        assertEquals(13758.0d, gnssAntennaInfo.getCarrierFrequencyMHz(), PRECISION);
-
-        // Phase Center Offset Tests --------------------------------------------------------
-        PhaseCenterOffset phaseCenterOffset =
-                gnssAntennaInfo.getPhaseCenterOffset();
-        assertEquals(4.3d, phaseCenterOffset.getXOffsetMm(),
-                PRECISION);
-        assertEquals(1.4d, phaseCenterOffset.getXOffsetUncertaintyMm(),
-                PRECISION);
-        assertEquals(2.10d, phaseCenterOffset.getYOffsetMm(),
-                PRECISION);
-        assertEquals(2.1d, phaseCenterOffset.getYOffsetUncertaintyMm(),
-                PRECISION);
-        assertEquals(3.12d, phaseCenterOffset.getZOffsetMm(),
-                PRECISION);
-        assertEquals(0.5d, phaseCenterOffset.getZOffsetUncertaintyMm(),
-                PRECISION);
-
-        // Phase Center Variation Corrections Tests -----------------------------------------
-        assertNull(gnssAntennaInfo.getPhaseCenterVariationCorrections());
-
-        // Signal Gain Corrections Tests -----------------------------------------------------
-        assertNull(gnssAntennaInfo.getSignalGainCorrections());
-    }
-
-    private static void verifyFullGnssAntennaInfoValuesAndGetters(GnssAntennaInfo gnssAntennaInfo) {
-        assertEquals(13758.0d, gnssAntennaInfo.getCarrierFrequencyMHz(), PRECISION);
-
-        // Phase Center Offset Tests --------------------------------------------------------
-        PhaseCenterOffset phaseCenterOffset =
-                gnssAntennaInfo.getPhaseCenterOffset();
-        assertEquals(4.3d, phaseCenterOffset.getXOffsetMm(),
-                PRECISION);
-        assertEquals(1.4d, phaseCenterOffset.getXOffsetUncertaintyMm(),
-                PRECISION);
-        assertEquals(2.10d, phaseCenterOffset.getYOffsetMm(),
-                PRECISION);
-        assertEquals(2.1d, phaseCenterOffset.getYOffsetUncertaintyMm(),
-                PRECISION);
-        assertEquals(3.12d, phaseCenterOffset.getZOffsetMm(),
-                PRECISION);
-        assertEquals(0.5d, phaseCenterOffset.getZOffsetUncertaintyMm(),
-                PRECISION);
-
-        // Phase Center Variation Corrections Tests -----------------------------------------
-        SphericalCorrections phaseCenterVariationCorrections =
-                gnssAntennaInfo.getPhaseCenterVariationCorrections();
-
-        assertEquals(60.0d, phaseCenterVariationCorrections.getDeltaTheta(), PRECISION);
-        assertEquals(36.0d, phaseCenterVariationCorrections.getDeltaPhi(), PRECISION);
-        assertArrayEquals(PHASE_CENTER_VARIATION_CORRECTIONS, phaseCenterVariationCorrections
-                .getCorrectionsArray());
-        assertArrayEquals(PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES,
-                phaseCenterVariationCorrections.getCorrectionUncertaintiesArray());
-
-        // Signal Gain Corrections Tests -----------------------------------------------------
-        SphericalCorrections signalGainCorrections = gnssAntennaInfo.getSignalGainCorrections();
-
-        assertEquals(60.0d, signalGainCorrections.getDeltaTheta(), PRECISION);
-        assertEquals(36.0d, signalGainCorrections.getDeltaPhi(), PRECISION);
-        assertArrayEquals(SIGNAL_GAIN_CORRECTIONS, signalGainCorrections
-                .getCorrectionsArray());
-        assertArrayEquals(SIGNAL_GAIN_CORRECTION_UNCERTAINTIES,
-                signalGainCorrections.getCorrectionUncertaintiesArray());
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java
deleted file mode 100644
index 2536890..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts.fine;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.location.GnssMeasurement;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class GnssMeasurementTest {
-
-    private static final double DELTA = 0.001;
-
-    @Test
-    public void testDescribeContents() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        assertEquals(0, measurement.describeContents());
-    }
-
-    @Test
-    public void testReset() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        measurement.reset();
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        setTestValues(measurement);
-        Parcel parcel = Parcel.obtain();
-        measurement.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
-        verifyTestValues(newMeasurement);
-        parcel.recycle();
-    }
-
-    @Test
-    public void testSet() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        setTestValues(measurement);
-        GnssMeasurement newMeasurement = new GnssMeasurement();
-        newMeasurement.set(measurement);
-        verifyTestValues(newMeasurement);
-    }
-
-    @Test
-    public void testSetReset() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        setTestValues(measurement);
-
-        assertTrue(measurement.hasCarrierCycles());
-        measurement.resetCarrierCycles();
-        assertFalse(measurement.hasCarrierCycles());
-
-        assertTrue(measurement.hasCarrierFrequencyHz());
-        measurement.resetCarrierFrequencyHz();
-        assertFalse(measurement.hasCarrierFrequencyHz());
-
-        assertTrue(measurement.hasCarrierPhase());
-        measurement.resetCarrierPhase();
-        assertFalse(measurement.hasCarrierPhase());
-
-        assertTrue(measurement.hasCarrierPhaseUncertainty());
-        measurement.resetCarrierPhaseUncertainty();
-        assertFalse(measurement.hasCarrierPhaseUncertainty());
-
-        assertTrue(measurement.hasSnrInDb());
-        measurement.resetSnrInDb();
-        assertFalse(measurement.hasSnrInDb());
-
-        assertTrue(measurement.hasCodeType());
-        measurement.resetCodeType();
-        assertFalse(measurement.hasCodeType());
-
-        assertTrue(measurement.hasBasebandCn0DbHz());
-        measurement.resetBasebandCn0DbHz();
-        assertFalse(measurement.hasBasebandCn0DbHz());
-
-        assertTrue(measurement.hasFullInterSignalBiasNanos());
-        measurement.resetFullInterSignalBiasNanos();
-        assertFalse(measurement.hasFullInterSignalBiasNanos());
-
-        assertTrue(measurement.hasFullInterSignalBiasUncertaintyNanos());
-        measurement.resetFullInterSignalBiasUncertaintyNanos();
-        assertFalse(measurement.hasFullInterSignalBiasUncertaintyNanos());
-
-        assertTrue(measurement.hasSatelliteInterSignalBiasNanos());
-        measurement.resetSatelliteInterSignalBiasNanos();
-        assertFalse(measurement.hasSatelliteInterSignalBiasNanos());
-
-        assertTrue(measurement.hasSatelliteInterSignalBiasUncertaintyNanos());
-        measurement.resetSatelliteInterSignalBiasUncertaintyNanos();
-        assertFalse(measurement.hasSatelliteInterSignalBiasUncertaintyNanos());
-    }
-
-    private static void setTestValues(GnssMeasurement measurement) {
-        measurement.setAccumulatedDeltaRangeMeters(1.0);
-        measurement.setAccumulatedDeltaRangeState(2);
-        measurement.setAccumulatedDeltaRangeUncertaintyMeters(3.0);
-        measurement.setBasebandCn0DbHz(3.0);
-        measurement.setCarrierCycles(4);
-        measurement.setCarrierFrequencyHz(5.0f);
-        measurement.setCarrierPhase(6.0);
-        measurement.setCarrierPhaseUncertainty(7.0);
-        measurement.setCn0DbHz(8.0);
-        measurement.setCodeType("C");
-        measurement.setConstellationType(GnssStatus.CONSTELLATION_GALILEO);
-        measurement.setMultipathIndicator(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED);
-        measurement.setPseudorangeRateMetersPerSecond(9.0);
-        measurement.setPseudorangeRateUncertaintyMetersPerSecond(10.0);
-        measurement.setReceivedSvTimeNanos(11);
-        measurement.setReceivedSvTimeUncertaintyNanos(12);
-        measurement.setFullInterSignalBiasNanos(1.3);
-        measurement.setFullInterSignalBiasUncertaintyNanos(2.5);
-        measurement.setSatelliteInterSignalBiasNanos(5.4);
-        measurement.setSatelliteInterSignalBiasUncertaintyNanos(10.0);
-        measurement.setSnrInDb(13.0);
-        measurement.setState(14);
-        measurement.setSvid(15);
-        measurement.setTimeOffsetNanos(16.0);
-    }
-
-    private static void verifyTestValues(GnssMeasurement measurement) {
-        assertEquals(1.0, measurement.getAccumulatedDeltaRangeMeters(), DELTA);
-        assertEquals(2, measurement.getAccumulatedDeltaRangeState());
-        assertEquals(3.0, measurement.getAccumulatedDeltaRangeUncertaintyMeters(), DELTA);
-        assertEquals(3.0, measurement.getBasebandCn0DbHz(), DELTA);
-        assertEquals(4, measurement.getCarrierCycles());
-        assertEquals(5.0f, measurement.getCarrierFrequencyHz(), DELTA);
-        assertEquals(6.0, measurement.getCarrierPhase(), DELTA);
-        assertEquals(7.0, measurement.getCarrierPhaseUncertainty(), DELTA);
-        assertEquals(8.0, measurement.getCn0DbHz(), DELTA);
-        assertEquals(GnssStatus.CONSTELLATION_GALILEO, measurement.getConstellationType());
-        assertEquals(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED,
-                measurement.getMultipathIndicator());
-        assertEquals("C", measurement.getCodeType());
-        assertEquals(9.0, measurement.getPseudorangeRateMetersPerSecond(), DELTA);
-        assertEquals(10.0, measurement.getPseudorangeRateUncertaintyMetersPerSecond(), DELTA);
-        assertEquals(11, measurement.getReceivedSvTimeNanos());
-        assertEquals(12, measurement.getReceivedSvTimeUncertaintyNanos());
-        assertEquals(1.3, measurement.getFullInterSignalBiasNanos(), DELTA);
-        assertEquals(2.5, measurement.getFullInterSignalBiasUncertaintyNanos(), DELTA);
-        assertEquals(5.4, measurement.getSatelliteInterSignalBiasNanos(), DELTA);
-        assertEquals(10.0, measurement.getSatelliteInterSignalBiasUncertaintyNanos(), DELTA);
-        assertEquals(13.0, measurement.getSnrInDb(), DELTA);
-        assertEquals(14, measurement.getState());
-        assertEquals(15, measurement.getSvid());
-        assertEquals(16.0, measurement.getTimeOffsetNanos(), DELTA);
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java
deleted file mode 100644
index 048c741..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts.fine;
-
-import static org.junit.Assert.assertEquals;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Collection;
-import java.util.Iterator;
-
-@RunWith(AndroidJUnit4.class)
-public class GnssMeasurementsEventTest {
-
-    @Test
-    public void testDescribeContents() {
-        GnssClock clock = new GnssClock();
-        GnssMeasurement m1 = new GnssMeasurement();
-        GnssMeasurement m2 = new GnssMeasurement();
-        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
-                clock, new GnssMeasurement[] {m1, m2});
-        assertEquals(0, event.describeContents());
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        GnssClock clock = new GnssClock();
-        clock.setLeapSecond(100);
-        GnssMeasurement m1 = new GnssMeasurement();
-        m1.setConstellationType(GnssStatus.CONSTELLATION_GLONASS);
-        GnssMeasurement m2 = new GnssMeasurement();
-        m2.setReceivedSvTimeNanos(43999);
-        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
-                clock, new GnssMeasurement[] {m1, m2});
-        Parcel parcel = Parcel.obtain();
-        event.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssMeasurementsEvent newEvent = GnssMeasurementsEvent.CREATOR.createFromParcel(parcel);
-        assertEquals(100, newEvent.getClock().getLeapSecond());
-        Collection<GnssMeasurement> measurements = newEvent.getMeasurements();
-        assertEquals(2, measurements.size());
-        Iterator<GnssMeasurement> iterator = measurements.iterator();
-        GnssMeasurement newM1 = iterator.next();
-        assertEquals(GnssStatus.CONSTELLATION_GLONASS, newM1.getConstellationType());
-        GnssMeasurement newM2 = iterator.next();
-        assertEquals(43999, newM2.getReceivedSvTimeNanos());
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssStatusTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssStatusTest.java
deleted file mode 100644
index df050ba..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/GnssStatusTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts.fine;
-
-import static org.junit.Assert.assertEquals;
-
-import android.location.GnssStatus;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class GnssStatusTest {
-
-    private static final float DELTA = 1e-3f;
-
-    @Test
-    public void testGetValues() {
-        GnssStatus gnssStatus = getTestGnssStatus();
-        verifyTestValues(gnssStatus);
-    }
-
-    @Test
-    public void testBuilder_ClearSatellites() {
-        GnssStatus.Builder builder = new GnssStatus.Builder();
-        builder.addSatellite(GnssStatus.CONSTELLATION_GPS,
-                /* svid= */ 13,
-                /* cn0DbHz= */ 25.5f,
-                /* elevation= */ 2.0f,
-                /* azimuth= */ 255.1f,
-                /* hasEphemeris= */ true,
-                /* hasAlmanac= */ false,
-                /* usedInFix= */ true,
-                /* hasCarrierFrequency= */ true,
-                /* carrierFrequency= */ 1575420000f,
-                /* hasBasebandCn0DbHz= */ true,
-                /* basebandCn0DbHz= */ 20.5f);
-        builder.clearSatellites();
-
-        GnssStatus status = builder.build();
-        assertEquals(0, status.getSatelliteCount());
-    }
-
-    private static GnssStatus getTestGnssStatus() {
-        GnssStatus.Builder builder = new GnssStatus.Builder();
-        builder.addSatellite(GnssStatus.CONSTELLATION_GPS,
-                /* svid= */ 13,
-                /* cn0DbHz= */ 25.5f,
-                /* elevation= */ 2.0f,
-                /* azimuth= */ 255.1f,
-                /* hasEphemeris= */ true,
-                /* hasAlmanac= */ false,
-                /* usedInFix= */ true,
-                /* hasCarrierFrequency= */ true,
-                /* carrierFrequency= */ 1575420000f,
-                /* hasBasebandCn0DbHz= */ true,
-                /* basebandCn0DbHz= */ 20.5f);
-
-        builder.addSatellite(GnssStatus.CONSTELLATION_GLONASS,
-                /* svid= */ 9,
-                /* cn0DbHz= */ 31.0f,
-                /* elevation= */ 1.0f,
-                /* azimuth= */ 193.8f,
-                /* hasEphemeris= */ false,
-                /* hasAlmanac= */ true,
-                /* usedInFix= */ false,
-                /* hasCarrierFrequency= */ false,
-                /* carrierFrequency= */ Float.NaN,
-                /* hasBasebandCn0DbHz= */ true,
-                /* basebandCn0DbHz= */ 26.9f);
-
-        return builder.build();
-    }
-
-    private static void verifyTestValues(GnssStatus gnssStatus) {
-        assertEquals(2, gnssStatus.getSatelliteCount());
-        assertEquals(GnssStatus.CONSTELLATION_GPS, gnssStatus.getConstellationType(0));
-        assertEquals(GnssStatus.CONSTELLATION_GLONASS, gnssStatus.getConstellationType(1));
-
-        assertEquals(13, gnssStatus.getSvid(0));
-        assertEquals(9, gnssStatus.getSvid(1));
-
-        assertEquals(25.5f, gnssStatus.getCn0DbHz(0), DELTA);
-        assertEquals(31.0f, gnssStatus.getCn0DbHz(1), DELTA);
-
-        assertEquals(2.0f, gnssStatus.getElevationDegrees(0), DELTA);
-        assertEquals(1.0f, gnssStatus.getElevationDegrees(1), DELTA);
-
-        assertEquals(255.1f, gnssStatus.getAzimuthDegrees(0), DELTA);
-        assertEquals(193.8f, gnssStatus.getAzimuthDegrees(1), DELTA);
-
-        assertEquals(true, gnssStatus.hasEphemerisData(0));
-        assertEquals(false, gnssStatus.hasEphemerisData(1));
-
-        assertEquals(false, gnssStatus.hasAlmanacData(0));
-        assertEquals(true, gnssStatus.hasAlmanacData(1));
-
-        assertEquals(true, gnssStatus.usedInFix(0));
-        assertEquals(false, gnssStatus.usedInFix(1));
-
-        assertEquals(true, gnssStatus.hasCarrierFrequencyHz(0));
-        assertEquals(false, gnssStatus.hasCarrierFrequencyHz(1));
-
-        assertEquals(1575420000f, gnssStatus.getCarrierFrequencyHz(0), DELTA);
-
-        assertEquals(true, gnssStatus.hasBasebandCn0DbHz(0));
-        assertEquals(true, gnssStatus.hasBasebandCn0DbHz(1));
-
-        assertEquals(20.5f, gnssStatus.getBasebandCn0DbHz(0), DELTA);
-        assertEquals(26.9f, gnssStatus.getBasebandCn0DbHz(1), DELTA);
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
index e6e69df..2307d73 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
@@ -16,6 +16,7 @@
 
 package android.location.cts.fine;
 
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.location.LocationManager.EXTRA_PROVIDER_ENABLED;
 import static android.location.LocationManager.EXTRA_PROVIDER_NAME;
 import static android.location.LocationManager.FUSED_PROVIDER;
@@ -23,22 +24,29 @@
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationManager.PASSIVE_PROVIDER;
 import static android.location.LocationManager.PROVIDERS_CHANGED_ACTION;
+import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+import static android.provider.Settings.Global.BATTERY_SAVER_CONSTANTS;
+import static android.provider.Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST;
 
 import static androidx.test.ext.truth.content.IntentSubject.assertThat;
 import static androidx.test.ext.truth.location.LocationSubject.assertThat;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.compatibility.common.util.LocationUtils.createLocation;
+import static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_GLOBAL;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.location.Criteria;
 import android.location.GnssAntennaInfo;
@@ -55,21 +63,21 @@
 import android.location.cts.common.GetCurrentLocationCapture;
 import android.location.cts.common.LocationListenerCapture;
 import android.location.cts.common.LocationPendingIntentCapture;
-import android.location.cts.common.ProximityPendingIntentCapture;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
 import android.os.HandlerThread;
 import android.os.Looper;
-import android.os.UserManager;
+import android.os.PowerManager;
 import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings.Secure;
 import android.util.Log;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.BatteryUtils;
 import com.android.compatibility.common.util.LocationUtils;
+import com.android.compatibility.common.util.ScreenUtils;
+import com.android.compatibility.common.util.ScreenUtils.ScreenResetter;
+import com.android.compatibility.common.util.SettingsUtils;
+import com.android.compatibility.common.util.SettingsUtils.SettingResetter;
 
 import org.junit.After;
 import org.junit.Before;
@@ -78,10 +86,9 @@
 
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Random;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class LocationManagerFineTest {
@@ -99,7 +106,7 @@
 
     @Before
     public void setUp() throws Exception {
-        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+        LocationUtils.registerMockLocationProvider(getInstrumentation(),
                 true);
 
         long seed = System.currentTimeMillis();
@@ -107,9 +114,7 @@
 
         mRandom = new Random(seed);
         mContext = ApplicationProvider.getApplicationContext();
-        mManager = mContext.getSystemService(LocationManager.class);
-
-        assertThat(mManager).isNotNull();
+        mManager = Objects.requireNonNull(mContext.getSystemService(LocationManager.class));
 
         for (String provider : mManager.getAllProviders()) {
             mManager.removeTestProvider(provider);
@@ -130,11 +135,13 @@
 
     @After
     public void tearDown() throws Exception {
-        for (String provider : mManager.getAllProviders()) {
-            mManager.removeTestProvider(provider);
+        if (mManager != null) {
+            for (String provider : mManager.getAllProviders()) {
+                mManager.removeTestProvider(provider);
+            }
         }
 
-        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+        LocationUtils.registerMockLocationProvider(getInstrumentation(),
                 false);
     }
 
@@ -144,14 +151,6 @@
     }
 
     @Test
-    public void testValidLocationMode() {
-        int locationMode = Secure.getInt(mContext.getContentResolver(), Secure.LOCATION_MODE,
-                Secure.LOCATION_MODE_OFF);
-        assertThat(locationMode).isNotEqualTo(Secure.LOCATION_MODE_SENSORS_ONLY);
-        assertThat(locationMode).isNotEqualTo(Secure.LOCATION_MODE_BATTERY_SAVING);
-    }
-
-    @Test
     public void testIsProviderEnabled() {
         assertThat(mManager.isProviderEnabled(TEST_PROVIDER)).isTrue();
 
@@ -221,6 +220,18 @@
     }
 
     @Test
+    public void testGetCurrentLocation_FreshOldLocation() throws Exception {
+        Location loc = createLocation(TEST_PROVIDER, mRandom);
+
+        mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+        try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+            mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+                    Executors.newSingleThreadExecutor(), capture);
+            assertThat(capture.getLocation(TIMEOUT_MS)).isEqualTo(loc);
+        }
+    }
+
+    @Test
     public void testGetCurrentLocation_DirectExecutor() throws Exception {
         Location loc = createLocation(TEST_PROVIDER, mRandom);
 
@@ -291,7 +302,7 @@
         }
 
         try {
-            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, (LocationListener) null);
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, null, Looper.getMainLooper());
             fail("Should throw IllegalArgumentException if listener is null!");
         } catch (IllegalArgumentException e) {
             // expected
@@ -305,7 +316,7 @@
         }
 
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(null, 0, 0, capture);
+            mManager.requestLocationUpdates(null, 0, 0, capture, Looper.getMainLooper());
             fail("Should throw IllegalArgumentException if provider is null!");
         } catch (IllegalArgumentException e) {
             // expected
@@ -428,7 +439,7 @@
                 true,
                 Criteria.POWER_LOW,
                 Criteria.ACCURACY_FINE);
-        setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
 
         Criteria criteria = new Criteria();
         criteria.setAccuracy(Criteria.ACCURACY_FINE);
@@ -503,12 +514,12 @@
         Location loc1 = createLocation(TEST_PROVIDER, mRandom);
         Location loc2 = createLocation(TEST_PROVIDER, mRandom);
 
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 0, 0,
-                false);
-        request.setNumUpdates(1);
-
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+            mManager.requestLocationUpdates(
+                    TEST_PROVIDER,
+                    new LocationRequest.Builder(0).setMaxUpdates(1).build(),
+                    Executors.newSingleThreadExecutor(),
+                    capture);
 
             mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
             assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
@@ -518,15 +529,16 @@
     }
 
     @Test
-    public void testRequestLocationUpdates_MinTime() throws Exception {
+    public void testRequestLocationUpdates_MinUpdateInterval() throws Exception {
         Location loc1 = createLocation(TEST_PROVIDER, mRandom);
         Location loc2 = createLocation(TEST_PROVIDER, mRandom);
 
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 5000,
-                0, false);
-
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+            mManager.requestLocationUpdates(
+                    TEST_PROVIDER,
+                    new LocationRequest.Builder(5000).build(),
+                    Executors.newSingleThreadExecutor(),
+                    capture);
 
             mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
             assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
@@ -536,15 +548,16 @@
     }
 
     @Test
-    public void testRequestLocationUpdates_MinDistance() throws Exception {
+    public void testRequestLocationUpdates_MinUpdateDistance() throws Exception {
         Location loc1 = createLocation(TEST_PROVIDER, 0, 0, 10);
         Location loc2 = createLocation(TEST_PROVIDER, 0, 1, 10);
 
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 0,
-                200000, false);
-
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+            mManager.requestLocationUpdates(
+                    TEST_PROVIDER,
+                    new LocationRequest.Builder(0).setMinUpdateDistanceMeters(200000).build(),
+                    Executors.newSingleThreadExecutor(),
+                    capture);
 
             mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
             assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
@@ -554,6 +567,180 @@
     }
 
     @Test
+    public void testRequestLocationUpdates_BatterySaver_GpsDisabledScreenOff() throws Exception {
+        PowerManager powerManager = Objects.requireNonNull(
+                mContext.getSystemService(PowerManager.class));
+
+        mManager.addTestProvider(GPS_PROVIDER,
+                false,
+                true,
+                false,
+                false,
+                true,
+                true,
+                true,
+                Criteria.POWER_HIGH,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(GPS_PROVIDER, true);
+
+        LocationRequest request = new LocationRequest.Builder(0).build();
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext);
+             ScreenResetter ignored = new ScreenResetter()) {
+            mManager.requestLocationUpdates(GPS_PROVIDER, request,
+                    Executors.newSingleThreadExecutor(), capture);
+            mManager.requestLocationUpdates(TEST_PROVIDER, request,
+                    Executors.newSingleThreadExecutor(), capture);
+
+            SettingsUtils.set(NAMESPACE_GLOBAL, BATTERY_SAVER_CONSTANTS,
+                    "gps_mode=1");
+            BatteryUtils.runDumpsysBatteryUnplug();
+            BatteryUtils.enableBatterySaver(true);
+            assertThat(powerManager.getLocationPowerSaveMode()).isEqualTo(
+                    LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF);
+
+            // check screen off behavior
+            ScreenUtils.setScreenOn(false);
+            assertFalse(powerManager.isInteractive());
+            Location loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(GPS_PROVIDER, loc);
+            assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+
+            // check screen on behavior
+            ScreenUtils.setScreenOn(true);
+            assertTrue(powerManager.isInteractive());
+            loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(GPS_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+        } finally {
+            BatteryUtils.enableBatterySaver(false);
+            BatteryUtils.runDumpsysBatteryReset();
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_BatterySaver_AllDisabledScreenOff() throws Exception {
+        PowerManager powerManager = Objects.requireNonNull(
+                mContext.getSystemService(PowerManager.class));
+
+        LocationRequest request = new LocationRequest.Builder(0).build();
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext);
+             ScreenResetter ignored = new ScreenResetter()) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, request,
+                    Executors.newSingleThreadExecutor(), capture);
+
+            SettingsUtils.set(NAMESPACE_GLOBAL, BATTERY_SAVER_CONSTANTS,
+                    "gps_mode=2");
+            BatteryUtils.runDumpsysBatteryUnplug();
+            BatteryUtils.enableBatterySaver(true);
+            assertThat(powerManager.getLocationPowerSaveMode()).isEqualTo(
+                    LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
+
+            // check screen off behavior
+            ScreenUtils.setScreenOn(false);
+            assertFalse(powerManager.isInteractive());
+            mManager.setTestProviderLocation(TEST_PROVIDER, createLocation(TEST_PROVIDER, mRandom));
+            assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+
+            // check screen on behavior
+            ScreenUtils.setScreenOn(true);
+            assertTrue(powerManager.isInteractive());
+            Location loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+        } finally {
+            BatteryUtils.enableBatterySaver(false);
+            BatteryUtils.runDumpsysBatteryReset();
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_BatterySaver_ThrottleScreenOff() throws Exception {
+        PowerManager powerManager = Objects.requireNonNull(
+                mContext.getSystemService(PowerManager.class));
+
+        LocationRequest request = new LocationRequest.Builder(0).build();
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext);
+             ScreenResetter ignored = new ScreenResetter()) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, request,
+                    Executors.newSingleThreadExecutor(), capture);
+
+            SettingsUtils.set(NAMESPACE_GLOBAL, BATTERY_SAVER_CONSTANTS,
+                    "gps_mode=4");
+            BatteryUtils.runDumpsysBatteryUnplug();
+            BatteryUtils.enableBatterySaver(true);
+            assertThat(powerManager.getLocationPowerSaveMode()).isEqualTo(
+                    LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF);
+
+            // check screen off behavior
+            ScreenUtils.setScreenOn(false);
+            assertFalse(powerManager.isInteractive());
+            mManager.setTestProviderLocation(TEST_PROVIDER, createLocation(TEST_PROVIDER, mRandom));
+            assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+
+            // check screen on behavior
+            ScreenUtils.setScreenOn(true);
+            assertTrue(powerManager.isInteractive());
+            Location loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+        } finally {
+            BatteryUtils.enableBatterySaver(false);
+            BatteryUtils.runDumpsysBatteryReset();
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_LocationSettingsIgnored() throws Exception {
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext);
+             ScreenResetter ignored1 = new ScreenResetter();
+             SettingResetter ignored2 = new SettingResetter(NAMESPACE_GLOBAL,
+                     LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST, mContext.getPackageName())) {
+
+            getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity(WRITE_SECURE_SETTINGS);
+            try {
+                mManager.requestLocationUpdates(
+                        TEST_PROVIDER,
+                        new LocationRequest.Builder(0)
+                                .setLocationSettingsIgnored(true)
+                                .build(),
+                        Executors.newSingleThreadExecutor(),
+                        capture);
+            } finally {
+                getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+            }
+
+            // turn off provider
+            mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+
+            // enable battery saver throttling
+            SettingsUtils.set(NAMESPACE_GLOBAL, BATTERY_SAVER_CONSTANTS,
+                    "gps_mode=4");
+            BatteryUtils.runDumpsysBatteryUnplug();
+            BatteryUtils.enableBatterySaver(true);
+            ScreenUtils.setScreenOn(false);
+
+            // test that all restrictions are bypassed
+            Location loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isEqualTo(loc);
+            loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isEqualTo(loc);
+        } finally {
+            BatteryUtils.enableBatterySaver(false);
+            BatteryUtils.runDumpsysBatteryReset();
+        }
+    }
+
+    @Test
     @AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS permission")
     public void testRequestGpsUpdates_B9758659() throws Exception {
         assumeTrue(hasGpsFeature());
@@ -574,15 +761,18 @@
                 true,
                 Criteria.POWER_LOW,
                 Criteria.ACCURACY_COARSE);
-        setTestProviderEnabled(NETWORK_PROVIDER, true);
+        mManager.setTestProviderEnabled(NETWORK_PROVIDER, true);
         mManager.setTestProviderLocation(NETWORK_PROVIDER, networkLocation);
 
         // reset gps provider to give it a cold start scenario
         mManager.sendExtraCommand(GPS_PROVIDER, "delete_aiding_data", null);
 
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(GPS_PROVIDER, 0, 0, false);
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+            mManager.requestLocationUpdates(
+                    GPS_PROVIDER,
+                    new LocationRequest.Builder(0).build(),
+                    Executors.newSingleThreadExecutor(),
+                    capture);
 
             Location location = capture.getNextLocation(TIMEOUT_MS);
             if (location != null) {
@@ -664,14 +854,14 @@
     }
 
     @Test
-    public void testGetProviders() throws Exception {
+    public void testGetProviders() {
         List<String> providers = mManager.getProviders(false);
         assertThat(providers.contains(TEST_PROVIDER)).isTrue();
 
         providers = mManager.getProviders(true);
         assertThat(providers.contains(TEST_PROVIDER)).isTrue();
 
-        setTestProviderEnabled(TEST_PROVIDER, false);
+        mManager.setTestProviderEnabled(TEST_PROVIDER, false);
 
         providers = mManager.getProviders(false);
         assertThat(providers.contains(TEST_PROVIDER)).isTrue();
@@ -700,7 +890,7 @@
     }
 
     @Test
-    public void testGetBestProvider() throws Exception {
+    public void testGetBestProvider() {
         List<String> allProviders = mManager.getAllProviders();
         Criteria criteria = new Criteria();
 
@@ -730,7 +920,7 @@
         criteria.setPowerRequirement(Criteria.POWER_LOW);
         assertThat(mManager.getBestProvider(criteria, false)).isEqualTo(TEST_PROVIDER);
 
-        setTestProviderEnabled(TEST_PROVIDER, false);
+        mManager.setTestProviderEnabled(TEST_PROVIDER, false);
         assertThat(mManager.getBestProvider(criteria, true)).isNotEqualTo(TEST_PROVIDER);
     }
 
@@ -885,7 +1075,7 @@
                     assertThat(received.isFromMockProvider()).isTrue();
                     assertThat(mManager.getLastKnownLocation(provider)).isEqualTo(loc1);
 
-                    setTestProviderEnabled(provider, false);
+                    mManager.setTestProviderEnabled(provider, false);
                     mManager.setTestProviderLocation(provider, loc2);
                     assertThat(mManager.getLastKnownLocation(provider)).isNull();
                     assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
@@ -979,130 +1169,6 @@
     }
 
     @Test
-    public void testAddProximityAlert() throws Exception {
-        if (isNotSystemUser()) {
-            Log.i(TAG, "Skipping test on secondary user");
-            return;
-        }
-
-        mManager.addTestProvider(FUSED_PROVIDER,
-                true,
-                false,
-                true,
-                false,
-                false,
-                false,
-                false,
-                Criteria.POWER_MEDIUM,
-                Criteria.ACCURACY_FINE);
-        setTestProviderEnabled(FUSED_PROVIDER, true);
-        mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 30, 30, 10));
-
-        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
-            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
-
-            // adding a proximity alert is asynchronous for no good reason, so we have to wait and
-            // hope the alert is added in the mean time.
-            Thread.sleep(500);
-
-            mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
-            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
-
-            mManager.setTestProviderLocation(FUSED_PROVIDER,
-                    createLocation(FUSED_PROVIDER, 30, 30, 10));
-            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
-        }
-
-        try {
-            mManager.addProximityAlert(0, 0, 1000, -1, null);
-            fail("Should throw IllegalArgumentException if pending intent is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
-            try {
-                mManager.addProximityAlert(0, 0, 0, -1, capture.getPendingIntent());
-                fail("Should throw IllegalArgumentException if radius == 0!");
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-
-            try {
-                mManager.addProximityAlert(0, 0, -1, -1, capture.getPendingIntent());
-                fail("Should throw IllegalArgumentException if radius < 0!");
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-
-            try {
-                mManager.addProximityAlert(1000, 1000, 1000, -1, capture.getPendingIntent());
-                fail("Should throw IllegalArgumentException if lat/lon are illegal!");
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-        }
-    }
-
-    @Test
-    public void testAddProximityAlert_StartProximate() throws Exception {
-        if (isNotSystemUser()) {
-            Log.i(TAG, "Skipping test on secondary user");
-            return;
-        }
-
-        mManager.addTestProvider(FUSED_PROVIDER,
-                true,
-                false,
-                true,
-                false,
-                false,
-                false,
-                false,
-                Criteria.POWER_MEDIUM,
-                Criteria.ACCURACY_FINE);
-        setTestProviderEnabled(FUSED_PROVIDER, true);
-        mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
-
-        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
-            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
-            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
-        }
-    }
-
-    @Test
-    public void testAddProximityAlert_Expires() throws Exception {
-        if (isNotSystemUser()) {
-            Log.i(TAG, "Skipping test on secondary user");
-            return;
-        }
-
-        mManager.addTestProvider(FUSED_PROVIDER,
-                true,
-                false,
-                true,
-                false,
-                false,
-                false,
-                false,
-                Criteria.POWER_MEDIUM,
-                Criteria.ACCURACY_FINE);
-        setTestProviderEnabled(FUSED_PROVIDER, true);
-        mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 30, 30, 10));
-
-        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
-            mManager.addProximityAlert(0, 0, 1000, 1, capture.getPendingIntent());
-
-            // adding a proximity alert is asynchronous for no good reason, so we have to wait and
-            // hope the alert is added in the mean time.
-            Thread.sleep(500);
-
-            mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
-            assertThat(capture.getNextProximityChange(FAILURE_TIMEOUT_MS)).isNull();
-        }
-    }
-
-    @Test
     public void testGetGnssYearOfHardware() {
         assumeTrue(hasGpsFeature());
         mManager.getGnssYearOfHardware();
@@ -1171,33 +1237,4 @@
         return mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LOCATION_GPS);
     }
-
-    private boolean isNotSystemUser() {
-        return !mContext.getSystemService(UserManager.class).isSystemUser();
-    }
-
-    private void setTestProviderEnabled(String provider, boolean enabled) throws InterruptedException {
-        // prior to R, setTestProviderEnabled is asynchronous, so we have to wait for provider
-        // state to settle.
-        if (VERSION.SDK_INT <= VERSION_CODES.Q) {
-            CountDownLatch latch = new CountDownLatch(1);
-            BroadcastReceiver receiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    latch.countDown();
-                }
-            };
-            mContext.registerReceiver(receiver,
-                    new IntentFilter(PROVIDERS_CHANGED_ACTION));
-            mManager.setTestProviderEnabled(provider, enabled);
-
-            // it's ok if this times out, as we don't notify for noop changes
-            if (!latch.await(500, TimeUnit.MILLISECONDS)) {
-                Log.i(TAG, "timeout while waiting for provider enabled change");
-            }
-            mContext.unregisterReceiver(receiver);
-        } else {
-            mManager.setTestProviderEnabled(provider, enabled);
-        }
-    }
 }
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java
deleted file mode 100644
index d28e2e0..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java
+++ /dev/null
@@ -1,540 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.fine;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.location.Location;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.util.StringBuilderPrinter;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.text.DecimalFormat;
-
-@RunWith(AndroidJUnit4.class)
-public class LocationTest {
-
-    private static final float DELTA = 0.1f;
-    private final float TEST_ACCURACY = 1.0f;
-    private final float TEST_VERTICAL_ACCURACY = 2.0f;
-    private final float TEST_SPEED_ACCURACY = 3.0f;
-    private final float TEST_BEARING_ACCURACY = 4.0f;
-    private final double TEST_ALTITUDE = 1.0;
-    private final double TEST_LATITUDE = 50;
-    private final float TEST_BEARING = 1.0f;
-    private final double TEST_LONGITUDE = 20;
-    private final float TEST_SPEED = 5.0f;
-    private final long TEST_TIME = 100;
-    private final String TEST_PROVIDER = "LocationProvider";
-    private final String TEST_KEY1NAME = "key1";
-    private final String TEST_KEY2NAME = "key2";
-    private final boolean TEST_KEY1VALUE = false;
-    private final byte TEST_KEY2VALUE = 10;
-
-    @Test
-    public void testConstructor() {
-        new Location("LocationProvider");
-
-        Location l = createTestLocation();
-        Location location = new Location(l);
-        assertTestLocation(location);
-
-        try {
-            new Location((Location) null);
-            fail("should throw NullPointerException");
-        } catch (NullPointerException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testDump() {
-        StringBuilder sb = new StringBuilder();
-        StringBuilderPrinter printer = new StringBuilderPrinter(sb);
-        Location location = new Location("LocationProvider");
-        location.dump(printer, "");
-        assertNotNull(sb.toString());
-    }
-
-    @Test
-    public void testBearingTo() {
-        Location location = new Location("");
-        Location dest = new Location("");
-
-        // set the location to Beijing
-        location.setLatitude(39.9);
-        location.setLongitude(116.4);
-        // set the destination to Chengdu
-        dest.setLatitude(30.7);
-        dest.setLongitude(104.1);
-        assertEquals(-128.66, location.bearingTo(dest), DELTA);
-
-        float bearing;
-        Location zeroLocation = new Location("");
-        zeroLocation.setLatitude(0);
-        zeroLocation.setLongitude(0);
-
-        Location testLocation = new Location("");
-        testLocation.setLatitude(0);
-        testLocation.setLongitude(150);
-
-        bearing = zeroLocation.bearingTo(zeroLocation);
-        assertEquals(0.0f, bearing, DELTA);
-
-        bearing = zeroLocation.bearingTo(testLocation);
-        assertEquals(90.0f, bearing, DELTA);
-
-        testLocation.setLatitude(90);
-        testLocation.setLongitude(0);
-        bearing = zeroLocation.bearingTo(testLocation);
-        assertEquals(0.0f, bearing, DELTA);
-
-        try {
-            location.bearingTo(null);
-            fail("should throw NullPointerException");
-        } catch (NullPointerException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testConvert_CoordinateToRepresentation() {
-        DecimalFormat df = new DecimalFormat("###.#####");
-        String result;
-
-        result = Location.convert(-80.0, Location.FORMAT_DEGREES);
-        assertEquals("-" + df.format(80.0), result);
-
-        result = Location.convert(-80.085, Location.FORMAT_MINUTES);
-        assertEquals("-80:" + df.format(5.1), result);
-
-        result = Location.convert(-80, Location.FORMAT_MINUTES);
-        assertEquals("-80:" + df.format(0), result);
-
-        result = Location.convert(-80.075, Location.FORMAT_MINUTES);
-        assertEquals("-80:" + df.format(4.5), result);
-
-        result = Location.convert(-80.075, Location.FORMAT_DEGREES);
-        assertEquals("-" + df.format(80.075), result);
-
-        result = Location.convert(-80.075, Location.FORMAT_SECONDS);
-        assertEquals("-80:4:30", result);
-
-        try {
-            Location.convert(-181, Location.FORMAT_SECONDS);
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            Location.convert(181, Location.FORMAT_SECONDS);
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            Location.convert(-80.075, -1);
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testConvert_RepresentationToCoordinate() {
-        double result;
-
-        result = Location.convert("-80.075");
-        assertEquals(-80.075, result, DELTA);
-
-        result = Location.convert("-80:05.10000");
-        assertEquals(-80.085, result, DELTA);
-
-        result = Location.convert("-80:04:03.00000");
-        assertEquals(-80.0675, result, DELTA);
-
-        result = Location.convert("-80:4:3");
-        assertEquals(-80.0675, result, DELTA);
-
-        try {
-            Location.convert(null);
-            fail("should throw NullPointerException.");
-        } catch (NullPointerException e){
-            // expected.
-        }
-
-        try {
-            Location.convert(":");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-
-        try {
-            Location.convert("190:4:3");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-
-        try {
-            Location.convert("-80:60:3");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-
-        try {
-            Location.convert("-80:4:60");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-    }
-
-    @Test
-    public void testDescribeContents() {
-        Location location = new Location("");
-        location.describeContents();
-    }
-
-    @Test
-    public void testDistanceBetween() {
-        float[] result = new float[3];
-        Location.distanceBetween(0, 0, 0, 0, result);
-        assertEquals(0.0, result[0], DELTA);
-        assertEquals(0.0, result[1], DELTA);
-        assertEquals(0.0, result[2], DELTA);
-
-        Location.distanceBetween(20, 30, -40, 140, result);
-        assertEquals(1.3094936E7, result[0], 1);
-        assertEquals(125.4538, result[1], DELTA);
-        assertEquals(93.3971, result[2], DELTA);
-
-        try {
-            Location.distanceBetween(20, 30, -40, 140, null);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            Location.distanceBetween(20, 30, -40, 140, new float[0]);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testDistanceTo() {
-        float distance;
-        Location zeroLocation = new Location("");
-        zeroLocation.setLatitude(0);
-        zeroLocation.setLongitude(0);
-
-        Location testLocation = new Location("");
-        testLocation.setLatitude(30);
-        testLocation.setLongitude(50);
-
-        distance = zeroLocation.distanceTo(zeroLocation);
-        assertEquals(0, distance, DELTA);
-
-        distance = zeroLocation.distanceTo(testLocation);
-        assertEquals(6244139.0, distance, 1);
-    }
-
-    @Test
-    public void testAccessAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasAccuracy());
-
-        location.setAccuracy(1.0f);
-        assertEquals(1.0, location.getAccuracy(), DELTA);
-        assertTrue(location.hasAccuracy());
-    }
-
-    @Test
-    public void testAccessVerticalAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasVerticalAccuracy());
-
-        location.setVerticalAccuracyMeters(1.0f);
-        assertEquals(1.0, location.getVerticalAccuracyMeters(), DELTA);
-        assertTrue(location.hasVerticalAccuracy());
-    }
-
-    @Test
-    public void testAccessSpeedAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasSpeedAccuracy());
-
-        location.setSpeedAccuracyMetersPerSecond(1.0f);
-        assertEquals(1.0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertTrue(location.hasSpeedAccuracy());
-    }
-
-    @Test
-    public void testAccessBearingAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasBearingAccuracy());
-
-        location.setBearingAccuracyDegrees(1.0f);
-        assertEquals(1.0, location.getBearingAccuracyDegrees(), DELTA);
-        assertTrue(location.hasBearingAccuracy());
-    }
-
-
-    @Test
-    public void testAccessAltitude() {
-        Location location = new Location("");
-        assertFalse(location.hasAltitude());
-
-        location.setAltitude(1.0);
-        assertEquals(1.0, location.getAltitude(), DELTA);
-        assertTrue(location.hasAltitude());
-    }
-
-    @Test
-    public void testAccessBearing() {
-        Location location = new Location("");
-        assertFalse(location.hasBearing());
-
-        location.setBearing(1.0f);
-        assertEquals(1.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-
-        location.setBearing(371.0f);
-        assertEquals(11.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-
-        location.setBearing(-361.0f);
-        assertEquals(359.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-    }
-
-    @Test
-    public void testAccessExtras() {
-        Location location = createTestLocation();
-
-        assertTestBundle(location.getExtras());
-
-        location.setExtras(null);
-        assertNull(location.getExtras());
-    }
-
-    @Test
-    public void testAccessLatitude() {
-        Location location = new Location("");
-
-        location.setLatitude(0);
-        assertEquals(0, location.getLatitude(), DELTA);
-
-        location.setLatitude(90);
-        assertEquals(90, location.getLatitude(), DELTA);
-
-        location.setLatitude(-90);
-        assertEquals(-90, location.getLatitude(), DELTA);
-    }
-
-    @Test
-    public void testAccessLongitude() {
-        Location location = new Location("");
-
-        location.setLongitude(0);
-        assertEquals(0, location.getLongitude(), DELTA);
-
-        location.setLongitude(180);
-        assertEquals(180, location.getLongitude(), DELTA);
-
-        location.setLongitude(-180);
-        assertEquals(-180, location.getLongitude(), DELTA);
-    }
-
-    @Test
-    public void testAccessProvider() {
-        Location location = new Location("");
-
-        String provider = "Location Provider";
-        location.setProvider(provider);
-        assertEquals(provider, location.getProvider());
-
-        location.setProvider(null);
-        assertNull(location.getProvider());
-    }
-
-    @Test
-    public void testAccessSpeed() {
-        Location location = new Location("");
-        assertFalse(location.hasSpeed());
-
-        location.setSpeed(234.0045f);
-        assertEquals(234.0045, location.getSpeed(), DELTA);
-        assertTrue(location.hasSpeed());
-    }
-
-    @Test
-    public void testAccessTime() {
-        Location location = new Location("");
-
-        location.setTime(0);
-        assertEquals(0, location.getTime());
-
-        location.setTime(Long.MAX_VALUE);
-        assertEquals(Long.MAX_VALUE, location.getTime());
-
-        location.setTime(12000);
-        assertEquals(12000, location.getTime());
-    }
-
-    @Test
-    public void testAccessElapsedRealtime() {
-        Location location = new Location("");
-
-        location.setElapsedRealtimeNanos(0);
-        assertEquals(0, location.getElapsedRealtimeNanos());
-
-        location.setElapsedRealtimeNanos(Long.MAX_VALUE);
-        assertEquals(Long.MAX_VALUE, location.getElapsedRealtimeNanos());
-
-        location.setElapsedRealtimeNanos(12000);
-        assertEquals(12000, location.getElapsedRealtimeNanos());
-    }
-
-    @Test
-    public void testAccessElapsedRealtimeUncertaintyNanos() {
-        Location location = new Location("");
-        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
-        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
-
-        location.setElapsedRealtimeUncertaintyNanos(12000.0);
-        assertEquals(12000.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
-        assertTrue(location.hasElapsedRealtimeUncertaintyNanos());
-
-        location.reset();
-        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
-        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
-    }
-
-    @Test
-    public void testSet() {
-        Location location = new Location("");
-
-        Location loc = createTestLocation();
-
-        location.set(loc);
-        assertTestLocation(location);
-
-        location.reset();
-        assertNull(location.getProvider());
-        assertEquals(0, location.getTime());
-        assertEquals(0, location.getLatitude(), DELTA);
-        assertEquals(0, location.getLongitude(), DELTA);
-        assertEquals(0, location.getAltitude(), DELTA);
-        assertFalse(location.hasAltitude());
-        assertEquals(0, location.getSpeed(), DELTA);
-        assertFalse(location.hasSpeed());
-        assertEquals(0, location.getBearing(), DELTA);
-        assertFalse(location.hasBearing());
-        assertEquals(0, location.getAccuracy(), DELTA);
-        assertFalse(location.hasAccuracy());
-
-        assertEquals(0, location.getVerticalAccuracyMeters(), DELTA);
-        assertEquals(0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertEquals(0, location.getBearingAccuracyDegrees(), DELTA);
-
-        assertFalse(location.hasVerticalAccuracy());
-        assertFalse(location.hasSpeedAccuracy());
-        assertFalse(location.hasBearingAccuracy());
-
-        assertNull(location.getExtras());
-    }
-
-    @Test
-    public void testToString() {
-        Location location = createTestLocation();
-
-        assertNotNull(location.toString());
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        Location location = createTestLocation();
-
-        Parcel parcel = Parcel.obtain();
-        location.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        Location newLocation = Location.CREATOR.createFromParcel(parcel);
-        assertTestLocation(newLocation);
-
-        parcel.recycle();
-    }
-
-    private void assertTestLocation(Location l) {
-        assertNotNull(l);
-        assertEquals(TEST_PROVIDER, l.getProvider());
-        assertEquals(TEST_ACCURACY, l.getAccuracy(), DELTA);
-        assertEquals(TEST_VERTICAL_ACCURACY, l.getVerticalAccuracyMeters(), DELTA);
-        assertEquals(TEST_SPEED_ACCURACY, l.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertEquals(TEST_BEARING_ACCURACY, l.getBearingAccuracyDegrees(), DELTA);
-        assertEquals(TEST_ALTITUDE, l.getAltitude(), DELTA);
-        assertEquals(TEST_LATITUDE, l.getLatitude(), DELTA);
-        assertEquals(TEST_BEARING, l.getBearing(), DELTA);
-        assertEquals(TEST_LONGITUDE, l.getLongitude(), DELTA);
-        assertEquals(TEST_SPEED, l.getSpeed(), DELTA);
-        assertEquals(TEST_TIME, l.getTime());
-        assertTestBundle(l.getExtras());
-    }
-
-    private Location createTestLocation() {
-        Location l = new Location(TEST_PROVIDER);
-        l.setAccuracy(TEST_ACCURACY);
-        l.setVerticalAccuracyMeters(TEST_VERTICAL_ACCURACY);
-        l.setSpeedAccuracyMetersPerSecond(TEST_SPEED_ACCURACY);
-        l.setBearingAccuracyDegrees(TEST_BEARING_ACCURACY);
-
-        l.setAltitude(TEST_ALTITUDE);
-        l.setLatitude(TEST_LATITUDE);
-        l.setBearing(TEST_BEARING);
-        l.setLongitude(TEST_LONGITUDE);
-        l.setSpeed(TEST_SPEED);
-        l.setTime(TEST_TIME);
-        Bundle bundle = new Bundle();
-        bundle.putBoolean(TEST_KEY1NAME, TEST_KEY1VALUE);
-        bundle.putByte(TEST_KEY2NAME, TEST_KEY2VALUE);
-        l.setExtras(bundle);
-
-        return l;
-    }
-
-    private void assertTestBundle(Bundle bundle) {
-        assertFalse(bundle.getBoolean(TEST_KEY1NAME));
-        assertEquals(TEST_KEY2VALUE, bundle.getByte(TEST_KEY2NAME));
-    }
-}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
index a908057..1e0cd91 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
@@ -45,6 +45,11 @@
     private static final int LOCATION_TO_COLLECT_COUNT = 8;
     private static final int PASSIVE_LOCATION_TO_COLLECT_COUNT = 100;
     private static final int TIMEOUT_IN_SEC = 120;
+    private static final long MILLIS_PER_NANO = 1_000_000;
+
+    // Maximum time drift between elapsedRealtime (Android SystemClock time) and utcTime (gps
+    // time calculated from the chipset).
+    private static final long MAX_TIME_DRIFT_MILLIS = 1000;
 
     // Minimum time interval between fixes in milliseconds.
     private static final int[] FIX_INTERVALS_MILLIS = {0, 1000, 5000, 15000};
@@ -116,6 +121,23 @@
         List<Location> activeLocations = activeLocationListener.getReceivedLocationList();
         List<Location> passiveLocations = passiveLocationListener.getReceivedLocationList();
         validateLocationUpdateInterval(activeLocations, passiveLocations, fixIntervalMillis);
+        validateTimeDriftBetweenUtcTimeAndElapsedRealtime(activeLocations);
+    }
+
+    private static void validateTimeDriftBetweenUtcTimeAndElapsedRealtime(
+            List<Location> activeLocations) {
+        SoftAssert softAssert = new SoftAssert(TAG);
+        long firstTimeDiff = (activeLocations.get(0).getElapsedRealtimeNanos()
+                / MILLIS_PER_NANO) - activeLocations.get(0).getTime();
+        for (int i = 1; i < activeLocations.size(); i++) {
+            long timeDiff = (activeLocations.get(i).getElapsedRealtimeNanos() / MILLIS_PER_NANO)
+                    - activeLocations.get(i).getTime();
+            long timeDrift = Math.abs(timeDiff - firstTimeDiff);
+            softAssert.assertTrue("Time drift between elapsedRealtime and utcTime must be bounded: "
+                            + timeDrift + " (max: " + MAX_TIME_DRIFT_MILLIS + ")",
+                    timeDrift < MAX_TIME_DRIFT_MILLIS);
+        }
+        softAssert.assertAll();
     }
 
     private static void validateLocationUpdateInterval(List<Location> activeLocations,
diff --git a/tests/location/location_none/Android.bp b/tests/location/location_none/Android.bp
index 74732aa..4bc7488 100644
--- a/tests/location/location_none/Android.bp
+++ b/tests/location/location_none/Android.bp
@@ -22,6 +22,8 @@
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
+        // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows:
+        "testng",
         "truth-prebuilt",
     ],
     libs: [
diff --git a/tests/location/location_none/src/android/location/cts/none/AddressTest.java b/tests/location/location_none/src/android/location/cts/none/AddressTest.java
new file mode 100644
index 0000000..3cdd139
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/AddressTest.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Locale;
+
+import android.location.Address;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AddressTest {
+
+    private static final double DELTA = 0.001;
+
+    @Test
+    public void testConstructor() {
+        new Address(Locale.ENGLISH);
+
+        new Address(Locale.FRANCE);
+
+        new Address(null);
+    }
+
+    @Test
+    public void testAccessAdminArea() {
+        Address address = new Address(Locale.ITALY);
+
+        String adminArea = "CA";
+        address.setAdminArea(adminArea);
+        assertEquals(adminArea, address.getAdminArea());
+
+        address.setAdminArea(null);
+        assertNull(address.getAdminArea());
+    }
+
+    @Test
+    public void testAccessCountryCode() {
+        Address address = new Address(Locale.JAPAN);
+
+        String countryCode = "US";
+        address.setCountryCode(countryCode);
+        assertEquals(countryCode, address.getCountryCode());
+
+        address.setCountryCode(null);
+        assertNull(address.getCountryCode());
+    }
+
+    @Test
+    public void testAccessCountryName() {
+        Address address = new Address(Locale.KOREA);
+
+        String countryName = "China";
+        address.setCountryName(countryName);
+        assertEquals(countryName, address.getCountryName());
+
+        address.setCountryName(null);
+        assertNull(address.getCountryName());
+    }
+
+    @Test
+    public void testAccessExtras() {
+        Address address = new Address(Locale.TAIWAN);
+
+        Bundle extras = new Bundle();
+        extras.putBoolean("key1", false);
+        byte b = 10;
+        extras.putByte("key2", b);
+
+        address.setExtras(extras);
+        Bundle actual = address.getExtras();
+        assertFalse(actual.getBoolean("key1"));
+        assertEquals(b, actual.getByte("key2"));
+
+        address.setExtras(null);
+        assertNull(address.getExtras());
+    }
+
+    @Test
+    public void testAccessFeatureName() {
+        Address address = new Address(Locale.SIMPLIFIED_CHINESE);
+
+        String featureName = "Golden Gate Bridge";
+        address.setFeatureName(featureName);
+        assertEquals(featureName, address.getFeatureName());
+
+        address.setFeatureName(null);
+        assertNull(address.getFeatureName());
+    }
+
+    @Test
+    public void testAccessLatitude() {
+        Address address = new Address(Locale.CHINA);
+        assertFalse(address.hasLatitude());
+
+        double latitude = 1.23456789;
+        address.setLatitude(latitude);
+        assertTrue(address.hasLatitude());
+        assertEquals(latitude, address.getLatitude(), DELTA);
+
+        address.clearLatitude();
+        assertFalse(address.hasLatitude());
+        try {
+            address.getLatitude();
+            fail("should throw IllegalStateException.");
+        } catch (IllegalStateException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testAccessLongitude() {
+        Address address = new Address(Locale.CHINA);
+        assertFalse(address.hasLongitude());
+
+        double longitude = 1.23456789;
+        address.setLongitude(longitude);
+        assertTrue(address.hasLongitude());
+        assertEquals(longitude, address.getLongitude(), DELTA);
+
+        address.clearLongitude();
+        assertFalse(address.hasLongitude());
+        try {
+            address.getLongitude();
+            fail("should throw IllegalStateException.");
+        } catch (IllegalStateException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testAccessPhone() {
+        Address address = new Address(Locale.CHINA);
+
+        String phone = "+86-13512345678";
+        address.setPhone(phone);
+        assertEquals(phone, address.getPhone());
+
+        address.setPhone(null);
+        assertNull(address.getPhone());
+    }
+
+    @Test
+    public void testAccessPostalCode() {
+        Address address = new Address(Locale.CHINA);
+
+        String postalCode = "93110";
+        address.setPostalCode(postalCode);
+        assertEquals(postalCode, address.getPostalCode());
+
+        address.setPostalCode(null);
+        assertNull(address.getPostalCode());
+    }
+
+    @Test
+    public void testAccessThoroughfare() {
+        Address address = new Address(Locale.CHINA);
+
+        String thoroughfare = "1600 Ampitheater Parkway";
+        address.setThoroughfare(thoroughfare);
+        assertEquals(thoroughfare, address.getThoroughfare());
+
+        address.setThoroughfare(null);
+        assertNull(address.getThoroughfare());
+    }
+
+    @Test
+    public void testAccessUrl() {
+        Address address = new Address(Locale.CHINA);
+
+        String Url = "Url";
+        address.setUrl(Url);
+        assertEquals(Url, address.getUrl());
+
+        address.setUrl(null);
+        assertNull(address.getUrl());
+    }
+
+    @Test
+    public void testAccessSubAdminArea() {
+        Address address = new Address(Locale.CHINA);
+
+        String subAdminArea = "Santa Clara County";
+        address.setSubAdminArea(subAdminArea);
+        assertEquals(subAdminArea, address.getSubAdminArea());
+
+        address.setSubAdminArea(null);
+        assertNull(address.getSubAdminArea());
+    }
+
+    @Test
+    public void testToString() {
+        Address address = new Address(Locale.CHINA);
+
+        address.setUrl("www.google.com");
+        address.setPostalCode("95120");
+        String expected = "Address[addressLines=[],feature=null,admin=null,sub-admin=null," +
+                "locality=null,thoroughfare=null,postalCode=95120,countryCode=null," +
+                "countryName=null,hasLatitude=false,latitude=0.0,hasLongitude=false," +
+                "longitude=0.0,phone=null,url=www.google.com,extras=null]";
+        assertEquals(expected, address.toString());
+    }
+
+    @Test
+    public void testAddressLine() {
+        Address address = new Address(Locale.CHINA);
+
+        try {
+            address.setAddressLine(-1, null);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+
+        try {
+            address.getAddressLine(-1);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+
+        address.setAddressLine(0, null);
+        assertNull(address.getAddressLine(0));
+        assertEquals(0, address.getMaxAddressLineIndex());
+
+        final String line1 = "1";
+        address.setAddressLine(0, line1);
+        assertEquals(line1, address.getAddressLine(0));
+        assertEquals(0, address.getMaxAddressLineIndex());
+
+        final String line2 = "2";
+        address.setAddressLine(5, line2);
+        assertEquals(line2, address.getAddressLine(5));
+        assertEquals(5, address.getMaxAddressLineIndex());
+
+        address.setAddressLine(2, null);
+        assertNull(address.getAddressLine(2));
+        assertEquals(5, address.getMaxAddressLineIndex());
+    }
+
+    @Test
+    public void testGetLocale() {
+        Locale locale = Locale.US;
+        Address address = new Address(locale);
+        assertSame(locale, address.getLocale());
+
+        locale = Locale.UK;
+        address = new Address(locale);
+        assertSame(locale, address.getLocale());
+
+        address = new Address(null);
+        assertNull(address.getLocale());
+    }
+
+    @Test
+    public void testAccessLocality() {
+        Address address = new Address(Locale.PRC);
+
+        String locality = "Hollywood";
+        address.setLocality(locality);
+        assertEquals(locality, address.getLocality());
+
+        address.setLocality(null);
+        assertNull(address.getLocality());
+    }
+
+    @Test
+    public void testAccessPremises() {
+        Address address = new Address(Locale.PRC);
+
+        String premises = "Appartment";
+        address.setPremises(premises);
+        assertEquals(premises, address.getPremises());
+
+        address.setPremises(null);
+        assertNull(address.getPremises());
+    }
+
+    @Test
+    public void testAccessSubLocality() {
+        Address address = new Address(Locale.PRC);
+
+        String subLocality = "Sarchnar";
+        address.setSubLocality(subLocality);
+        assertEquals(subLocality, address.getSubLocality());
+
+        address.setSubLocality(null);
+        assertNull(address.getSubLocality());
+    }
+
+    @Test
+    public void testAccessSubThoroughfare() {
+        Address address = new Address(Locale.PRC);
+
+        String subThoroughfare = "1600";
+        address.setSubThoroughfare(subThoroughfare);
+        assertEquals(subThoroughfare, address.getSubThoroughfare());
+
+        address.setSubThoroughfare(null);
+        assertNull(address.getSubThoroughfare());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        Locale locale = Locale.KOREA;
+        Address address = new Address(locale);
+
+        Parcel parcel = Parcel.obtain();
+        address.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        assertEquals(locale.getLanguage(), parcel.readString());
+        assertEquals(locale.getCountry(), parcel.readString());
+        assertEquals(0, parcel.readInt());
+        assertEquals(address.getFeatureName(), parcel.readString());
+        assertEquals(address.getAdminArea(), parcel.readString());
+        assertEquals(address.getSubAdminArea(), parcel.readString());
+        assertEquals(address.getLocality(), parcel.readString());
+        assertEquals(address.getSubLocality(), parcel.readString());
+        assertEquals(address.getThoroughfare(), parcel.readString());
+        assertEquals(address.getSubThoroughfare(), parcel.readString());
+        assertEquals(address.getPremises(), parcel.readString());
+        assertEquals(address.getPostalCode(), parcel.readString());
+        assertEquals(address.getCountryCode(), parcel.readString());
+        assertEquals(address.getCountryName(), parcel.readString());
+        assertEquals(0, parcel.readInt());
+        assertEquals(0, parcel.readInt());
+        assertEquals(address.getPhone(), parcel.readString());
+        assertEquals(address.getUrl(), parcel.readString());
+        assertEquals(address.getExtras(), parcel.readBundle());
+
+        parcel.recycle();
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/CriteriaTest.java b/tests/location/location_none/src/android/location/cts/none/CriteriaTest.java
new file mode 100644
index 0000000..5ece9b4
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/CriteriaTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.none;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.location.Criteria;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CriteriaTest {
+
+    @Test
+    public void testConstructor() {
+        new Criteria();
+
+        Criteria c = new Criteria();
+        c.setAccuracy(Criteria.ACCURACY_FINE);
+        c.setAltitudeRequired(true);
+        c.setBearingRequired(true);
+        c.setCostAllowed(true);
+        c.setPowerRequirement(Criteria.POWER_HIGH);
+        c.setSpeedRequired(true);
+        Criteria criteria = new Criteria(c);
+        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
+        assertTrue(criteria.isAltitudeRequired());
+        assertTrue(criteria.isBearingRequired());
+        assertTrue(criteria.isCostAllowed());
+        assertTrue(criteria.isSpeedRequired());
+        assertEquals(Criteria.POWER_HIGH, criteria.getPowerRequirement());
+
+        try {
+            new Criteria(null);
+            fail("should throw NullPointerException.");
+        } catch (NullPointerException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testDescribeContents() {
+        Criteria criteria = new Criteria();
+        criteria.describeContents();
+    }
+
+    @Test
+    public void testAccessAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setAccuracy(Criteria.ACCURACY_FINE);
+        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
+
+        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+        assertEquals(Criteria.ACCURACY_COARSE, criteria.getAccuracy());
+
+        try {
+            // It should throw IllegalArgumentException
+            criteria.setAccuracy(-1);
+            // issue 1728526
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            // It should throw IllegalArgumentException
+            criteria.setAccuracy(Criteria.ACCURACY_COARSE + 1);
+            // issue 1728526
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testAccessPowerRequirement() {
+        Criteria criteria = new Criteria();
+
+        criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getPowerRequirement());
+
+        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+        assertEquals(Criteria.POWER_MEDIUM, criteria.getPowerRequirement());
+
+        try {
+            criteria.setPowerRequirement(-1);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            criteria.setPowerRequirement(Criteria.POWER_HIGH + 1);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testAccessAltitudeRequired() {
+        Criteria criteria = new Criteria();
+
+        criteria.setAltitudeRequired(false);
+        assertFalse(criteria.isAltitudeRequired());
+
+        criteria.setAltitudeRequired(true);
+        assertTrue(criteria.isAltitudeRequired());
+    }
+
+    @Test
+    public void testAccessBearingAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setBearingAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getBearingAccuracy());
+
+        criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getBearingAccuracy());
+
+        criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getBearingAccuracy());
+      }
+
+    @Test
+    public void testAccessBearingRequired() {
+        Criteria criteria = new Criteria();
+
+        criteria.setBearingRequired(false);
+        assertFalse(criteria.isBearingRequired());
+
+        criteria.setBearingRequired(true);
+        assertTrue(criteria.isBearingRequired());
+    }
+
+    @Test
+    public void testAccessCostAllowed() {
+        Criteria criteria = new Criteria();
+
+        criteria.setCostAllowed(false);
+        assertFalse(criteria.isCostAllowed());
+
+        criteria.setCostAllowed(true);
+        assertTrue(criteria.isCostAllowed());
+    }
+
+    @Test
+    public void testAccessHorizontalAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getHorizontalAccuracy());
+
+        criteria.setHorizontalAccuracy(Criteria.ACCURACY_MEDIUM);
+        assertEquals(Criteria.ACCURACY_MEDIUM, criteria.getHorizontalAccuracy());
+
+        criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getHorizontalAccuracy());
+
+        criteria.setHorizontalAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getHorizontalAccuracy());
+    }
+
+    @Test
+    public void testAccessSpeedAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setSpeedAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getSpeedAccuracy());
+
+        criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getSpeedAccuracy());
+
+        criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getSpeedAccuracy());
+    }
+
+    @Test
+    public void testAccessSpeedRequired() {
+        Criteria criteria = new Criteria();
+
+        criteria.setSpeedRequired(false);
+        assertFalse(criteria.isSpeedRequired());
+
+        criteria.setSpeedRequired(true);
+        assertTrue(criteria.isSpeedRequired());
+    }
+
+    @Test
+    public void testAccessVerticalAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setVerticalAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getVerticalAccuracy());
+
+       criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getVerticalAccuracy());
+
+        criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getVerticalAccuracy());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        Criteria criteria = new Criteria();
+        criteria.setAltitudeRequired(true);
+        criteria.setBearingRequired(false);
+        criteria.setCostAllowed(true);
+        criteria.setSpeedRequired(true);
+
+        Parcel parcel = Parcel.obtain();
+        criteria.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        Criteria newCriteria = Criteria.CREATOR.createFromParcel(parcel);
+
+        assertEquals(criteria.getAccuracy(), newCriteria.getAccuracy());
+        assertEquals(criteria.getPowerRequirement(), newCriteria.getPowerRequirement());
+        assertEquals(criteria.isAltitudeRequired(), newCriteria.isAltitudeRequired());
+        assertEquals(criteria.isBearingRequired(), newCriteria.isBearingRequired());
+        assertEquals(criteria.isSpeedRequired(), newCriteria.isSpeedRequired());
+        assertEquals(criteria.isCostAllowed(), newCriteria.isCostAllowed());
+
+        parcel.recycle();
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java b/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java
new file mode 100644
index 0000000..584a26e
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.none;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.location.GnssAntennaInfo;
+import android.location.GnssAntennaInfo.PhaseCenterOffset;
+import android.location.GnssAntennaInfo.SphericalCorrections;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of GnssAntennaInfo class. This includes writing and reading from
+ * parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssAntennaInfoTest {
+
+    private static final double PRECISION = 0.0001;
+    private static final double[][] PHASE_CENTER_VARIATION_CORRECTIONS = new double[][]{
+        {5.29, 0.20, 7.15, 10.18, 9.47, 8.05},
+        {11.93, 3.98, 2.68, 2.66, 8.15, 13.54},
+        {14.69, 7.63, 13.46, 8.70, 4.36, 1.21},
+        {4.19, 12.43, 12.40, 0.90, 1.96, 1.99},
+        {7.30, 0.49, 7.43, 8.71, 3.70, 7.24},
+        {4.79, 1.88, 13.88, 3.52, 13.40, 11.81}
+    };
+    private static final double[][] PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES = new double[][]{
+            {1.77, 0.81, 0.72, 1.65, 2.35, 1.22},
+            {0.77, 3.43, 2.77, 0.97, 4.55, 1.38},
+            {1.51, 2.50, 2.23, 2.43, 1.94, 0.90},
+            {0.34, 4.72, 4.14, 4.78, 4.57, 1.69},
+            {4.49, 0.05, 2.78, 1.33, 3.20, 2.75},
+            {1.09, 0.31, 3.79, 4.32, 0.65, 1.23}
+    };
+    private static final double[][] SIGNAL_GAIN_CORRECTIONS = new double[][]{
+            {0.19, 7.04, 1.65, 14.84, 2.95, 9.21},
+            {0.45, 6.27, 14.57, 8.95, 3.92, 12.68},
+            {6.80, 13.04, 7.92, 2.23, 14.22, 7.36},
+            {4.81, 11.78, 5.04, 5.13, 12.09, 12.85},
+            {0.88, 4.04, 5.71, 3.72, 12.62, 0.40},
+            {14.26, 9.50, 4.21, 11.14, 6.54, 14.63}
+    };
+    private static final double[][] SIGNAL_GAIN_CORRECTION_UNCERTAINTIES = new double[][]{
+            {4.74, 1.54, 1.59, 4.05, 1.65, 2.46},
+            {0.10, 0.33, 0.84, 0.83, 0.57, 2.66},
+            {2.08, 1.46, 2.10, 3.25, 1.48, 0.65},
+            {4.02, 2.90, 2.51, 2.13, 1.67, 1.23},
+            {2.13, 4.30, 1.36, 3.86, 1.02, 2.96},
+            {3.22, 3.95, 3.75, 1.73, 1.91, 4.93}
+
+    };
+
+    @Test
+    public void testFullAntennaInfoDescribeContents() {
+        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
+        assertEquals(0, gnssAntennaInfo.describeContents());
+    }
+
+    @Test
+    public void testPartialAntennaInfoDescribeContents() {
+        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
+        assertEquals(0, gnssAntennaInfo.describeContents());
+    }
+
+    @Test
+    public void testFullAntennaInfoWriteToParcel() {
+        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
+        Parcel parcel = Parcel.obtain();
+        gnssAntennaInfo.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssAntennaInfo newGnssAntennaInfo = GnssAntennaInfo.CREATOR.createFromParcel(parcel);
+        verifyFullGnssAntennaInfoValuesAndGetters(newGnssAntennaInfo);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testPartialAntennaInfoWriteToParcel() {
+        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
+        Parcel parcel = Parcel.obtain();
+        gnssAntennaInfo.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssAntennaInfo newGnssAntennaInfo = GnssAntennaInfo.CREATOR.createFromParcel(parcel);
+        verifyPartialGnssAntennaInfoValuesAndGetters(newGnssAntennaInfo);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testCreateFullGnssAntennaInfoAndGetValues() {
+        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
+        verifyFullGnssAntennaInfoValuesAndGetters(gnssAntennaInfo);
+    }
+
+    @Test
+    public void testCreatePartialGnssAntennaInfoAndGetValues() {
+        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
+        verifyPartialGnssAntennaInfoValuesAndGetters(gnssAntennaInfo);
+    }
+
+    private static GnssAntennaInfo createFullTestGnssAntennaInfo() {
+        double carrierFrequencyMHz = 13758.0;
+
+        GnssAntennaInfo.PhaseCenterOffset phaseCenterOffset = new
+                GnssAntennaInfo.PhaseCenterOffset(
+                        4.3d,
+                    1.4d,
+                    2.10d,
+                    2.1d,
+                    3.12d,
+                    0.5d);
+
+        double[][] phaseCenterVariationCorrectionsMillimeters = PHASE_CENTER_VARIATION_CORRECTIONS;
+        double[][] phaseCenterVariationCorrectionsUncertaintyMillimeters =
+                PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES;
+        SphericalCorrections
+                phaseCenterVariationCorrections =
+                new SphericalCorrections(
+                        phaseCenterVariationCorrectionsMillimeters,
+                        phaseCenterVariationCorrectionsUncertaintyMillimeters);
+
+        double[][] signalGainCorrectionsDbi = SIGNAL_GAIN_CORRECTIONS;
+        double[][] signalGainCorrectionsUncertaintyDbi = SIGNAL_GAIN_CORRECTION_UNCERTAINTIES;
+        SphericalCorrections signalGainCorrections = new
+                SphericalCorrections(
+                signalGainCorrectionsDbi,
+                signalGainCorrectionsUncertaintyDbi);
+
+        return new GnssAntennaInfo.Builder()
+                .setCarrierFrequencyMHz(carrierFrequencyMHz)
+                .setPhaseCenterOffset(phaseCenterOffset)
+                .setPhaseCenterVariationCorrections(phaseCenterVariationCorrections)
+                .setSignalGainCorrections(signalGainCorrections)
+                .build();
+    }
+
+    private static GnssAntennaInfo createPartialTestGnssAntennaInfo() {
+        double carrierFrequencyMHz = 13758.0;
+
+        GnssAntennaInfo.PhaseCenterOffset phaseCenterOffset = new
+                GnssAntennaInfo.PhaseCenterOffset(
+                4.3d,
+                1.4d,
+                2.10d,
+                2.1d,
+                3.12d,
+                0.5d);
+
+        return new GnssAntennaInfo.Builder()
+                .setCarrierFrequencyMHz(carrierFrequencyMHz)
+                .setPhaseCenterOffset(phaseCenterOffset)
+                .build();
+    }
+
+    private static void verifyPartialGnssAntennaInfoValuesAndGetters(GnssAntennaInfo gnssAntennaInfo) {
+        assertEquals(13758.0d, gnssAntennaInfo.getCarrierFrequencyMHz(), PRECISION);
+
+        // Phase Center Offset Tests --------------------------------------------------------
+        PhaseCenterOffset phaseCenterOffset =
+                gnssAntennaInfo.getPhaseCenterOffset();
+        assertEquals(4.3d, phaseCenterOffset.getXOffsetMm(),
+                PRECISION);
+        assertEquals(1.4d, phaseCenterOffset.getXOffsetUncertaintyMm(),
+                PRECISION);
+        assertEquals(2.10d, phaseCenterOffset.getYOffsetMm(),
+                PRECISION);
+        assertEquals(2.1d, phaseCenterOffset.getYOffsetUncertaintyMm(),
+                PRECISION);
+        assertEquals(3.12d, phaseCenterOffset.getZOffsetMm(),
+                PRECISION);
+        assertEquals(0.5d, phaseCenterOffset.getZOffsetUncertaintyMm(),
+                PRECISION);
+
+        // Phase Center Variation Corrections Tests -----------------------------------------
+        assertNull(gnssAntennaInfo.getPhaseCenterVariationCorrections());
+
+        // Signal Gain Corrections Tests -----------------------------------------------------
+        assertNull(gnssAntennaInfo.getSignalGainCorrections());
+    }
+
+    private static void verifyFullGnssAntennaInfoValuesAndGetters(GnssAntennaInfo gnssAntennaInfo) {
+        assertEquals(13758.0d, gnssAntennaInfo.getCarrierFrequencyMHz(), PRECISION);
+
+        // Phase Center Offset Tests --------------------------------------------------------
+        PhaseCenterOffset phaseCenterOffset =
+                gnssAntennaInfo.getPhaseCenterOffset();
+        assertEquals(4.3d, phaseCenterOffset.getXOffsetMm(),
+                PRECISION);
+        assertEquals(1.4d, phaseCenterOffset.getXOffsetUncertaintyMm(),
+                PRECISION);
+        assertEquals(2.10d, phaseCenterOffset.getYOffsetMm(),
+                PRECISION);
+        assertEquals(2.1d, phaseCenterOffset.getYOffsetUncertaintyMm(),
+                PRECISION);
+        assertEquals(3.12d, phaseCenterOffset.getZOffsetMm(),
+                PRECISION);
+        assertEquals(0.5d, phaseCenterOffset.getZOffsetUncertaintyMm(),
+                PRECISION);
+
+        // Phase Center Variation Corrections Tests -----------------------------------------
+        SphericalCorrections phaseCenterVariationCorrections =
+                gnssAntennaInfo.getPhaseCenterVariationCorrections();
+
+        assertEquals(60.0d, phaseCenterVariationCorrections.getDeltaTheta(), PRECISION);
+        assertEquals(36.0d, phaseCenterVariationCorrections.getDeltaPhi(), PRECISION);
+        assertArrayEquals(PHASE_CENTER_VARIATION_CORRECTIONS, phaseCenterVariationCorrections
+                .getCorrectionsArray());
+        assertArrayEquals(PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES,
+                phaseCenterVariationCorrections.getCorrectionUncertaintiesArray());
+
+        // Signal Gain Corrections Tests -----------------------------------------------------
+        SphericalCorrections signalGainCorrections = gnssAntennaInfo.getSignalGainCorrections();
+
+        assertEquals(60.0d, signalGainCorrections.getDeltaTheta(), PRECISION);
+        assertEquals(36.0d, signalGainCorrections.getDeltaPhi(), PRECISION);
+        assertArrayEquals(SIGNAL_GAIN_CORRECTIONS, signalGainCorrections
+                .getCorrectionsArray());
+        assertArrayEquals(SIGNAL_GAIN_CORRECTION_UNCERTAINTIES,
+                signalGainCorrections.getCorrectionUncertaintiesArray());
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementTest.java b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementTest.java
new file mode 100644
index 0000000..e90e697
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.location.GnssMeasurement;
+import android.location.GnssStatus;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementTest {
+
+    private static final double DELTA = 0.001;
+
+    @Test
+    public void testDescribeContents() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        assertEquals(0, measurement.describeContents());
+    }
+
+    @Test
+    public void testReset() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        measurement.reset();
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+        Parcel parcel = Parcel.obtain();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
+        verifyTestValues(newMeasurement);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testSet() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+        GnssMeasurement newMeasurement = new GnssMeasurement();
+        newMeasurement.set(measurement);
+        verifyTestValues(newMeasurement);
+    }
+
+    @Test
+    public void testSetReset() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+
+        assertTrue(measurement.hasCarrierCycles());
+        measurement.resetCarrierCycles();
+        assertFalse(measurement.hasCarrierCycles());
+
+        assertTrue(measurement.hasCarrierFrequencyHz());
+        measurement.resetCarrierFrequencyHz();
+        assertFalse(measurement.hasCarrierFrequencyHz());
+
+        assertTrue(measurement.hasCarrierPhase());
+        measurement.resetCarrierPhase();
+        assertFalse(measurement.hasCarrierPhase());
+
+        assertTrue(measurement.hasCarrierPhaseUncertainty());
+        measurement.resetCarrierPhaseUncertainty();
+        assertFalse(measurement.hasCarrierPhaseUncertainty());
+
+        assertTrue(measurement.hasSnrInDb());
+        measurement.resetSnrInDb();
+        assertFalse(measurement.hasSnrInDb());
+
+        assertTrue(measurement.hasCodeType());
+        measurement.resetCodeType();
+        assertFalse(measurement.hasCodeType());
+
+        assertTrue(measurement.hasBasebandCn0DbHz());
+        measurement.resetBasebandCn0DbHz();
+        assertFalse(measurement.hasBasebandCn0DbHz());
+
+        assertTrue(measurement.hasFullInterSignalBiasNanos());
+        measurement.resetFullInterSignalBiasNanos();
+        assertFalse(measurement.hasFullInterSignalBiasNanos());
+
+        assertTrue(measurement.hasFullInterSignalBiasUncertaintyNanos());
+        measurement.resetFullInterSignalBiasUncertaintyNanos();
+        assertFalse(measurement.hasFullInterSignalBiasUncertaintyNanos());
+
+        assertTrue(measurement.hasSatelliteInterSignalBiasNanos());
+        measurement.resetSatelliteInterSignalBiasNanos();
+        assertFalse(measurement.hasSatelliteInterSignalBiasNanos());
+
+        assertTrue(measurement.hasSatelliteInterSignalBiasUncertaintyNanos());
+        measurement.resetSatelliteInterSignalBiasUncertaintyNanos();
+        assertFalse(measurement.hasSatelliteInterSignalBiasUncertaintyNanos());
+    }
+
+    private static void setTestValues(GnssMeasurement measurement) {
+        measurement.setAccumulatedDeltaRangeMeters(1.0);
+        measurement.setAccumulatedDeltaRangeState(2);
+        measurement.setAccumulatedDeltaRangeUncertaintyMeters(3.0);
+        measurement.setBasebandCn0DbHz(3.0);
+        measurement.setCarrierCycles(4);
+        measurement.setCarrierFrequencyHz(5.0f);
+        measurement.setCarrierPhase(6.0);
+        measurement.setCarrierPhaseUncertainty(7.0);
+        measurement.setCn0DbHz(8.0);
+        measurement.setCodeType("C");
+        measurement.setConstellationType(GnssStatus.CONSTELLATION_GALILEO);
+        measurement.setMultipathIndicator(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED);
+        measurement.setPseudorangeRateMetersPerSecond(9.0);
+        measurement.setPseudorangeRateUncertaintyMetersPerSecond(10.0);
+        measurement.setReceivedSvTimeNanos(11);
+        measurement.setReceivedSvTimeUncertaintyNanos(12);
+        measurement.setFullInterSignalBiasNanos(1.3);
+        measurement.setFullInterSignalBiasUncertaintyNanos(2.5);
+        measurement.setSatelliteInterSignalBiasNanos(5.4);
+        measurement.setSatelliteInterSignalBiasUncertaintyNanos(10.0);
+        measurement.setSnrInDb(13.0);
+        measurement.setState(14);
+        measurement.setSvid(15);
+        measurement.setTimeOffsetNanos(16.0);
+    }
+
+    private static void verifyTestValues(GnssMeasurement measurement) {
+        assertEquals(1.0, measurement.getAccumulatedDeltaRangeMeters(), DELTA);
+        assertEquals(2, measurement.getAccumulatedDeltaRangeState());
+        assertEquals(3.0, measurement.getAccumulatedDeltaRangeUncertaintyMeters(), DELTA);
+        assertEquals(3.0, measurement.getBasebandCn0DbHz(), DELTA);
+        assertEquals(4, measurement.getCarrierCycles());
+        assertEquals(5.0f, measurement.getCarrierFrequencyHz(), DELTA);
+        assertEquals(6.0, measurement.getCarrierPhase(), DELTA);
+        assertEquals(7.0, measurement.getCarrierPhaseUncertainty(), DELTA);
+        assertEquals(8.0, measurement.getCn0DbHz(), DELTA);
+        assertEquals(GnssStatus.CONSTELLATION_GALILEO, measurement.getConstellationType());
+        assertEquals(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED,
+                measurement.getMultipathIndicator());
+        assertEquals("C", measurement.getCodeType());
+        assertEquals(9.0, measurement.getPseudorangeRateMetersPerSecond(), DELTA);
+        assertEquals(10.0, measurement.getPseudorangeRateUncertaintyMetersPerSecond(), DELTA);
+        assertEquals(11, measurement.getReceivedSvTimeNanos());
+        assertEquals(12, measurement.getReceivedSvTimeUncertaintyNanos());
+        assertEquals(1.3, measurement.getFullInterSignalBiasNanos(), DELTA);
+        assertEquals(2.5, measurement.getFullInterSignalBiasUncertaintyNanos(), DELTA);
+        assertEquals(5.4, measurement.getSatelliteInterSignalBiasNanos(), DELTA);
+        assertEquals(10.0, measurement.getSatelliteInterSignalBiasUncertaintyNanos(), DELTA);
+        assertEquals(13.0, measurement.getSnrInDb(), DELTA);
+        assertEquals(14, measurement.getState());
+        assertEquals(15, measurement.getSvid());
+        assertEquals(16.0, measurement.getTimeOffsetNanos(), DELTA);
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java
new file mode 100644
index 0000000..dc1c56f
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+
+import android.location.GnssClock;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementsEventTest {
+
+    @Test
+    public void testDescribeContents() {
+        GnssClock clock = new GnssClock();
+        GnssMeasurement m1 = new GnssMeasurement();
+        GnssMeasurement m2 = new GnssMeasurement();
+        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
+                clock, new GnssMeasurement[] {m1, m2});
+        assertEquals(0, event.describeContents());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssClock clock = new GnssClock();
+        clock.setLeapSecond(100);
+        GnssMeasurement m1 = new GnssMeasurement();
+        m1.setConstellationType(GnssStatus.CONSTELLATION_GLONASS);
+        GnssMeasurement m2 = new GnssMeasurement();
+        m2.setReceivedSvTimeNanos(43999);
+        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
+                clock, new GnssMeasurement[] {m1, m2});
+        Parcel parcel = Parcel.obtain();
+        event.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurementsEvent newEvent = GnssMeasurementsEvent.CREATOR.createFromParcel(parcel);
+        assertEquals(100, newEvent.getClock().getLeapSecond());
+        Collection<GnssMeasurement> measurements = newEvent.getMeasurements();
+        assertEquals(2, measurements.size());
+        Iterator<GnssMeasurement> iterator = measurements.iterator();
+        GnssMeasurement newM1 = iterator.next();
+        assertEquals(GnssStatus.CONSTELLATION_GLONASS, newM1.getConstellationType());
+        GnssMeasurement newM2 = iterator.next();
+        assertEquals(43999, newM2.getReceivedSvTimeNanos());
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssStatusTest.java b/tests/location/location_none/src/android/location/cts/none/GnssStatusTest.java
new file mode 100644
index 0000000..24fb513
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssStatusTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+
+import android.location.GnssStatus;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssStatusTest {
+
+    private static final float DELTA = 1e-3f;
+
+    @Test
+    public void testGetValues() {
+        GnssStatus gnssStatus = getTestGnssStatus();
+        verifyTestValues(gnssStatus);
+    }
+
+    @Test
+    public void testBuilder_ClearSatellites() {
+        GnssStatus.Builder builder = new GnssStatus.Builder();
+        builder.addSatellite(GnssStatus.CONSTELLATION_GPS,
+                /* svid= */ 13,
+                /* cn0DbHz= */ 25.5f,
+                /* elevation= */ 2.0f,
+                /* azimuth= */ 255.1f,
+                /* hasEphemeris= */ true,
+                /* hasAlmanac= */ false,
+                /* usedInFix= */ true,
+                /* hasCarrierFrequency= */ true,
+                /* carrierFrequency= */ 1575420000f,
+                /* hasBasebandCn0DbHz= */ true,
+                /* basebandCn0DbHz= */ 20.5f);
+        builder.clearSatellites();
+
+        GnssStatus status = builder.build();
+        assertEquals(0, status.getSatelliteCount());
+    }
+
+    @Test
+    public void testRoundtrip() {
+        GnssStatus gnssStatus = getTestGnssStatus();
+
+        Parcel parcel = Parcel.obtain();
+        gnssStatus.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        GnssStatus fromParcel = GnssStatus.CREATOR.createFromParcel(parcel);
+        assertEquals(gnssStatus, fromParcel);
+    }
+
+    private static GnssStatus getTestGnssStatus() {
+        GnssStatus.Builder builder = new GnssStatus.Builder();
+        builder.addSatellite(GnssStatus.CONSTELLATION_GPS,
+                /* svid= */ 13,
+                /* cn0DbHz= */ 25.5f,
+                /* elevation= */ 2.0f,
+                /* azimuth= */ 255.1f,
+                /* hasEphemeris= */ true,
+                /* hasAlmanac= */ false,
+                /* usedInFix= */ true,
+                /* hasCarrierFrequency= */ true,
+                /* carrierFrequency= */ 1575420000f,
+                /* hasBasebandCn0DbHz= */ true,
+                /* basebandCn0DbHz= */ 20.5f);
+
+        builder.addSatellite(GnssStatus.CONSTELLATION_GLONASS,
+                /* svid= */ 9,
+                /* cn0DbHz= */ 31.0f,
+                /* elevation= */ 1.0f,
+                /* azimuth= */ 193.8f,
+                /* hasEphemeris= */ false,
+                /* hasAlmanac= */ true,
+                /* usedInFix= */ false,
+                /* hasCarrierFrequency= */ false,
+                /* carrierFrequency= */ Float.NaN,
+                /* hasBasebandCn0DbHz= */ true,
+                /* basebandCn0DbHz= */ 26.9f);
+
+        return builder.build();
+    }
+
+    private static void verifyTestValues(GnssStatus gnssStatus) {
+        assertEquals(2, gnssStatus.getSatelliteCount());
+        assertEquals(GnssStatus.CONSTELLATION_GPS, gnssStatus.getConstellationType(0));
+        assertEquals(GnssStatus.CONSTELLATION_GLONASS, gnssStatus.getConstellationType(1));
+
+        assertEquals(13, gnssStatus.getSvid(0));
+        assertEquals(9, gnssStatus.getSvid(1));
+
+        assertEquals(25.5f, gnssStatus.getCn0DbHz(0), DELTA);
+        assertEquals(31.0f, gnssStatus.getCn0DbHz(1), DELTA);
+
+        assertEquals(2.0f, gnssStatus.getElevationDegrees(0), DELTA);
+        assertEquals(1.0f, gnssStatus.getElevationDegrees(1), DELTA);
+
+        assertEquals(255.1f, gnssStatus.getAzimuthDegrees(0), DELTA);
+        assertEquals(193.8f, gnssStatus.getAzimuthDegrees(1), DELTA);
+
+        assertEquals(true, gnssStatus.hasEphemerisData(0));
+        assertEquals(false, gnssStatus.hasEphemerisData(1));
+
+        assertEquals(false, gnssStatus.hasAlmanacData(0));
+        assertEquals(true, gnssStatus.hasAlmanacData(1));
+
+        assertEquals(true, gnssStatus.usedInFix(0));
+        assertEquals(false, gnssStatus.usedInFix(1));
+
+        assertEquals(true, gnssStatus.hasCarrierFrequencyHz(0));
+        assertEquals(false, gnssStatus.hasCarrierFrequencyHz(1));
+
+        assertEquals(1575420000f, gnssStatus.getCarrierFrequencyHz(0), DELTA);
+
+        assertEquals(true, gnssStatus.hasBasebandCn0DbHz(0));
+        assertEquals(true, gnssStatus.hasBasebandCn0DbHz(1));
+
+        assertEquals(20.5f, gnssStatus.getBasebandCn0DbHz(0), DELTA);
+        assertEquals(26.9f, gnssStatus.getBasebandCn0DbHz(1), DELTA);
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java b/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java
new file mode 100644
index 0000000..8f283b9
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.none;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.location.LocationRequest;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationRequestTest {
+
+    @Test
+    public void testBuild_Defaults() {
+        LocationRequest request = new LocationRequest.Builder(0).build();
+        assertThat(request.getIntervalMillis()).isEqualTo(0);
+        assertThat(request.getMinUpdateIntervalMillis()).isEqualTo(0);
+        assertThat(request.getDurationMillis()).isEqualTo(Long.MAX_VALUE);
+        assertThat(request.getMaxUpdates()).isEqualTo(Integer.MAX_VALUE);
+        assertThat(request.getMinUpdateDistanceMeters()).isEqualTo(0f);
+        assertThat(request.isHiddenFromAppOps()).isEqualTo(false);
+        assertThat(request.isLocationSettingsIgnored()).isEqualTo(false);
+        assertThat(request.isLowPower()).isEqualTo(false);
+    }
+
+    @Test
+    public void testBuild_Explicit() {
+        LocationRequest request = new LocationRequest.Builder(5000)
+                .setMinUpdateIntervalMillis(4000)
+                .setDurationMillis(6000)
+                .setMaxUpdates(7000)
+                .setMinUpdateDistanceMeters(8000f)
+                .setHiddenFromAppOps(true)
+                .setLocationSettingsIgnored(true)
+                .setLowPower(true)
+                .build();
+        assertThat(request.getIntervalMillis()).isEqualTo(5000);
+        assertThat(request.getMinUpdateIntervalMillis()).isEqualTo(4000);
+        assertThat(request.getDurationMillis()).isEqualTo(6000);
+        assertThat(request.getMaxUpdates()).isEqualTo(7000);
+        assertThat(request.getMinUpdateDistanceMeters()).isEqualTo(8000f);
+        assertThat(request.isHiddenFromAppOps()).isEqualTo(true);
+        assertThat(request.isLocationSettingsIgnored()).isEqualTo(true);
+        assertThat(request.isLowPower()).isEqualTo(true);
+    }
+
+    @Test
+    public void testBuild_Copy() {
+        LocationRequest original = new LocationRequest.Builder(5000)
+                .setMinUpdateIntervalMillis(4000)
+                .setDurationMillis(6000)
+                .setMaxUpdates(7000)
+                .setMinUpdateDistanceMeters(8000f)
+                .setHiddenFromAppOps(true)
+                .setLocationSettingsIgnored(true)
+                .setLowPower(true)
+                .build();
+        LocationRequest copy = new LocationRequest.Builder(original).build();
+        assertThat(copy.getIntervalMillis()).isEqualTo(5000);
+        assertThat(copy.getMinUpdateIntervalMillis()).isEqualTo(4000);
+        assertThat(copy.getDurationMillis()).isEqualTo(6000);
+        assertThat(copy.getMaxUpdates()).isEqualTo(7000);
+        assertThat(copy.getMinUpdateDistanceMeters()).isEqualTo(8000f);
+        assertThat(copy.isHiddenFromAppOps()).isEqualTo(true);
+        assertThat(copy.isLocationSettingsIgnored()).isEqualTo(true);
+        assertThat(copy.isLowPower()).isEqualTo(true);
+        assertThat(copy).isEqualTo(original);
+    }
+
+    @Test
+    public void testBuild_ImplicitMinUpdateInterval() {
+        LocationRequest.Builder builder = new LocationRequest.Builder(5000);
+        assertThat(builder.build().getMinUpdateIntervalMillis()).isEqualTo(5000);
+
+        builder.setIntervalMillis(6000);
+        assertThat(builder.build().getMinUpdateIntervalMillis()).isEqualTo(6000);
+    }
+
+    @Test
+    public void testBuild_ClearMinUpdateInterval() {
+        LocationRequest request = new LocationRequest.Builder(5000)
+                .setMinUpdateIntervalMillis(4000)
+                .clearMinUpdateIntervalMillis()
+                .build();
+        assertThat(request.getMinUpdateIntervalMillis()).isEqualTo(5000);
+    }
+
+    @Test
+    public void testBuild_BadMinUpdateInterval() {
+        LocationRequest request = new LocationRequest.Builder(5000)
+                .setMinUpdateIntervalMillis(6000)
+                .build();
+        assertThat(request.getMinUpdateIntervalMillis()).isEqualTo(5000);
+    }
+
+    @Test
+    public void testBuild_IllegalInterval() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new LocationRequest.Builder(-1));
+    }
+
+    @Test
+    public void testBuild_IllegalMinUpdateInterval() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new LocationRequest.Builder(0).setMinUpdateIntervalMillis(-1));
+    }
+
+    @Test
+    public void testBuild_IllegalDuration() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new LocationRequest.Builder(0).setDurationMillis(0));
+    }
+
+    @Test
+    public void testBuild_IllegalMaxUpdates() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new LocationRequest.Builder(0).setMaxUpdates(0));
+    }
+
+    @Test
+    public void testBuild_IllegalMinUpdateDistance() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new LocationRequest.Builder(0).setMinUpdateDistanceMeters(-1));
+    }
+
+    @Test
+    public void testDescribeContents() {
+        LocationRequest request = new LocationRequest.Builder(0).build();
+        assertThat(request.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void testParcelRoundtrip() {
+        LocationRequest request = new LocationRequest.Builder(5000)
+                .setMinUpdateIntervalMillis(4000)
+                .setDurationMillis(6000)
+                .setMaxUpdates(7000)
+                .setMinUpdateDistanceMeters(8000f)
+                .setHiddenFromAppOps(true)
+                .setLocationSettingsIgnored(true)
+                .setLowPower(true)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        try {
+            request.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            assertThat(LocationRequest.CREATOR.createFromParcel(parcel)).isEqualTo(request);
+        } finally {
+            parcel.recycle();
+        }
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/LocationTest.java b/tests/location/location_none/src/android/location/cts/none/LocationTest.java
new file mode 100644
index 0000000..9a7ced0
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/LocationTest.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.location.Location;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.util.StringBuilderPrinter;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.text.DecimalFormat;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationTest {
+
+    private static final float DELTA = 0.1f;
+    private final float TEST_ACCURACY = 1.0f;
+    private final float TEST_VERTICAL_ACCURACY = 2.0f;
+    private final float TEST_SPEED_ACCURACY = 3.0f;
+    private final float TEST_BEARING_ACCURACY = 4.0f;
+    private final double TEST_ALTITUDE = 1.0;
+    private final double TEST_LATITUDE = 50;
+    private final float TEST_BEARING = 1.0f;
+    private final double TEST_LONGITUDE = 20;
+    private final float TEST_SPEED = 5.0f;
+    private final long TEST_TIME = 100;
+    private final String TEST_PROVIDER = "LocationProvider";
+    private final String TEST_KEY1NAME = "key1";
+    private final String TEST_KEY2NAME = "key2";
+    private final boolean TEST_KEY1VALUE = false;
+    private final byte TEST_KEY2VALUE = 10;
+
+    @Test
+    public void testConstructor() {
+        new Location("LocationProvider");
+
+        Location l = createTestLocation();
+        Location location = new Location(l);
+        assertTestLocation(location);
+
+        try {
+            new Location((Location) null);
+            fail("should throw NullPointerException");
+        } catch (NullPointerException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testDump() {
+        StringBuilder sb = new StringBuilder();
+        StringBuilderPrinter printer = new StringBuilderPrinter(sb);
+        Location location = new Location("LocationProvider");
+        location.dump(printer, "");
+        assertNotNull(sb.toString());
+    }
+
+    @Test
+    public void testBearingTo() {
+        Location location = new Location("");
+        Location dest = new Location("");
+
+        // set the location to Beijing
+        location.setLatitude(39.9);
+        location.setLongitude(116.4);
+        // set the destination to Chengdu
+        dest.setLatitude(30.7);
+        dest.setLongitude(104.1);
+        assertEquals(-128.66, location.bearingTo(dest), DELTA);
+
+        float bearing;
+        Location zeroLocation = new Location("");
+        zeroLocation.setLatitude(0);
+        zeroLocation.setLongitude(0);
+
+        Location testLocation = new Location("");
+        testLocation.setLatitude(0);
+        testLocation.setLongitude(150);
+
+        bearing = zeroLocation.bearingTo(zeroLocation);
+        assertEquals(0.0f, bearing, DELTA);
+
+        bearing = zeroLocation.bearingTo(testLocation);
+        assertEquals(90.0f, bearing, DELTA);
+
+        testLocation.setLatitude(90);
+        testLocation.setLongitude(0);
+        bearing = zeroLocation.bearingTo(testLocation);
+        assertEquals(0.0f, bearing, DELTA);
+
+        try {
+            location.bearingTo(null);
+            fail("should throw NullPointerException");
+        } catch (NullPointerException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testConvert_CoordinateToRepresentation() {
+        DecimalFormat df = new DecimalFormat("###.#####");
+        String result;
+
+        result = Location.convert(-80.0, Location.FORMAT_DEGREES);
+        assertEquals("-" + df.format(80.0), result);
+
+        result = Location.convert(-80.085, Location.FORMAT_MINUTES);
+        assertEquals("-80:" + df.format(5.1), result);
+
+        result = Location.convert(-80, Location.FORMAT_MINUTES);
+        assertEquals("-80:" + df.format(0), result);
+
+        result = Location.convert(-80.075, Location.FORMAT_MINUTES);
+        assertEquals("-80:" + df.format(4.5), result);
+
+        result = Location.convert(-80.075, Location.FORMAT_DEGREES);
+        assertEquals("-" + df.format(80.075), result);
+
+        result = Location.convert(-80.075, Location.FORMAT_SECONDS);
+        assertEquals("-80:4:30", result);
+
+        try {
+            Location.convert(-181, Location.FORMAT_SECONDS);
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            Location.convert(181, Location.FORMAT_SECONDS);
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            Location.convert(-80.075, -1);
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testConvert_RepresentationToCoordinate() {
+        double result;
+
+        result = Location.convert("-80.075");
+        assertEquals(-80.075, result, DELTA);
+
+        result = Location.convert("-80:05.10000");
+        assertEquals(-80.085, result, DELTA);
+
+        result = Location.convert("-80:04:03.00000");
+        assertEquals(-80.0675, result, DELTA);
+
+        result = Location.convert("-80:4:3");
+        assertEquals(-80.0675, result, DELTA);
+
+        try {
+            Location.convert(null);
+            fail("should throw NullPointerException.");
+        } catch (NullPointerException e){
+            // expected.
+        }
+
+        try {
+            Location.convert(":");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+
+        try {
+            Location.convert("190:4:3");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+
+        try {
+            Location.convert("-80:60:3");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+
+        try {
+            Location.convert("-80:4:60");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+    }
+
+    @Test
+    public void testDescribeContents() {
+        Location location = new Location("");
+        location.describeContents();
+    }
+
+    @Test
+    public void testDistanceBetween() {
+        float[] result = new float[3];
+        Location.distanceBetween(0, 0, 0, 0, result);
+        assertEquals(0.0, result[0], DELTA);
+        assertEquals(0.0, result[1], DELTA);
+        assertEquals(0.0, result[2], DELTA);
+
+        Location.distanceBetween(20, 30, -40, 140, result);
+        assertEquals(1.3094936E7, result[0], 1);
+        assertEquals(125.4538, result[1], DELTA);
+        assertEquals(93.3971, result[2], DELTA);
+
+        try {
+            Location.distanceBetween(20, 30, -40, 140, null);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            Location.distanceBetween(20, 30, -40, 140, new float[0]);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testDistanceTo() {
+        float distance;
+        Location zeroLocation = new Location("");
+        zeroLocation.setLatitude(0);
+        zeroLocation.setLongitude(0);
+
+        Location testLocation = new Location("");
+        testLocation.setLatitude(30);
+        testLocation.setLongitude(50);
+
+        distance = zeroLocation.distanceTo(zeroLocation);
+        assertEquals(0, distance, DELTA);
+
+        distance = zeroLocation.distanceTo(testLocation);
+        assertEquals(6244139.0, distance, 1);
+    }
+
+    @Test
+    public void testAccessAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasAccuracy());
+
+        location.setAccuracy(1.0f);
+        assertEquals(1.0, location.getAccuracy(), DELTA);
+        assertTrue(location.hasAccuracy());
+    }
+
+    @Test
+    public void testAccessVerticalAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasVerticalAccuracy());
+
+        location.setVerticalAccuracyMeters(1.0f);
+        assertEquals(1.0, location.getVerticalAccuracyMeters(), DELTA);
+        assertTrue(location.hasVerticalAccuracy());
+    }
+
+    @Test
+    public void testAccessSpeedAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasSpeedAccuracy());
+
+        location.setSpeedAccuracyMetersPerSecond(1.0f);
+        assertEquals(1.0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
+        assertTrue(location.hasSpeedAccuracy());
+    }
+
+    @Test
+    public void testAccessBearingAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasBearingAccuracy());
+
+        location.setBearingAccuracyDegrees(1.0f);
+        assertEquals(1.0, location.getBearingAccuracyDegrees(), DELTA);
+        assertTrue(location.hasBearingAccuracy());
+    }
+
+
+    @Test
+    public void testAccessAltitude() {
+        Location location = new Location("");
+        assertFalse(location.hasAltitude());
+
+        location.setAltitude(1.0);
+        assertEquals(1.0, location.getAltitude(), DELTA);
+        assertTrue(location.hasAltitude());
+    }
+
+    @Test
+    public void testAccessBearing() {
+        Location location = new Location("");
+        assertFalse(location.hasBearing());
+
+        location.setBearing(1.0f);
+        assertEquals(1.0, location.getBearing(), DELTA);
+        assertTrue(location.hasBearing());
+
+        location.setBearing(371.0f);
+        assertEquals(11.0, location.getBearing(), DELTA);
+        assertTrue(location.hasBearing());
+
+        location.setBearing(-361.0f);
+        assertEquals(359.0, location.getBearing(), DELTA);
+        assertTrue(location.hasBearing());
+    }
+
+    @Test
+    public void testAccessExtras() {
+        Location location = createTestLocation();
+
+        assertTestBundle(location.getExtras());
+
+        location.setExtras(null);
+        assertNull(location.getExtras());
+    }
+
+    @Test
+    public void testAccessLatitude() {
+        Location location = new Location("");
+
+        location.setLatitude(0);
+        assertEquals(0, location.getLatitude(), DELTA);
+
+        location.setLatitude(90);
+        assertEquals(90, location.getLatitude(), DELTA);
+
+        location.setLatitude(-90);
+        assertEquals(-90, location.getLatitude(), DELTA);
+    }
+
+    @Test
+    public void testAccessLongitude() {
+        Location location = new Location("");
+
+        location.setLongitude(0);
+        assertEquals(0, location.getLongitude(), DELTA);
+
+        location.setLongitude(180);
+        assertEquals(180, location.getLongitude(), DELTA);
+
+        location.setLongitude(-180);
+        assertEquals(-180, location.getLongitude(), DELTA);
+    }
+
+    @Test
+    public void testAccessProvider() {
+        Location location = new Location("");
+
+        String provider = "Location Provider";
+        location.setProvider(provider);
+        assertEquals(provider, location.getProvider());
+
+        location.setProvider(null);
+        assertNull(location.getProvider());
+    }
+
+    @Test
+    public void testAccessSpeed() {
+        Location location = new Location("");
+        assertFalse(location.hasSpeed());
+
+        location.setSpeed(234.0045f);
+        assertEquals(234.0045, location.getSpeed(), DELTA);
+        assertTrue(location.hasSpeed());
+    }
+
+    @Test
+    public void testAccessTime() {
+        Location location = new Location("");
+
+        location.setTime(0);
+        assertEquals(0, location.getTime());
+
+        location.setTime(Long.MAX_VALUE);
+        assertEquals(Long.MAX_VALUE, location.getTime());
+
+        location.setTime(12000);
+        assertEquals(12000, location.getTime());
+    }
+
+    @Test
+    public void testAccessElapsedRealtime() {
+        Location location = new Location("");
+
+        location.setElapsedRealtimeNanos(0);
+        assertEquals(0, location.getElapsedRealtimeNanos());
+
+        location.setElapsedRealtimeNanos(Long.MAX_VALUE);
+        assertEquals(Long.MAX_VALUE, location.getElapsedRealtimeNanos());
+
+        location.setElapsedRealtimeNanos(12000);
+        assertEquals(12000, location.getElapsedRealtimeNanos());
+    }
+
+    @Test
+    public void testAccessElapsedRealtimeUncertaintyNanos() {
+        Location location = new Location("");
+        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
+        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
+
+        location.setElapsedRealtimeUncertaintyNanos(12000.0);
+        assertEquals(12000.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
+        assertTrue(location.hasElapsedRealtimeUncertaintyNanos());
+
+        location.reset();
+        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
+        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
+    }
+
+    @Test
+    public void testSet() {
+        Location location = new Location("");
+
+        Location loc = createTestLocation();
+
+        location.set(loc);
+        assertTestLocation(location);
+
+        location.reset();
+        assertNull(location.getProvider());
+        assertEquals(0, location.getTime());
+        assertEquals(0, location.getLatitude(), DELTA);
+        assertEquals(0, location.getLongitude(), DELTA);
+        assertEquals(0, location.getAltitude(), DELTA);
+        assertFalse(location.hasAltitude());
+        assertEquals(0, location.getSpeed(), DELTA);
+        assertFalse(location.hasSpeed());
+        assertEquals(0, location.getBearing(), DELTA);
+        assertFalse(location.hasBearing());
+        assertEquals(0, location.getAccuracy(), DELTA);
+        assertFalse(location.hasAccuracy());
+
+        assertEquals(0, location.getVerticalAccuracyMeters(), DELTA);
+        assertEquals(0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
+        assertEquals(0, location.getBearingAccuracyDegrees(), DELTA);
+
+        assertFalse(location.hasVerticalAccuracy());
+        assertFalse(location.hasSpeedAccuracy());
+        assertFalse(location.hasBearingAccuracy());
+
+        assertNull(location.getExtras());
+    }
+
+    @Test
+    public void testToString() {
+        Location location = createTestLocation();
+
+        assertNotNull(location.toString());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        Location location = createTestLocation();
+
+        Parcel parcel = Parcel.obtain();
+        location.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        Location newLocation = Location.CREATOR.createFromParcel(parcel);
+        assertTestLocation(newLocation);
+
+        parcel.recycle();
+    }
+
+    private void assertTestLocation(Location l) {
+        assertNotNull(l);
+        assertEquals(TEST_PROVIDER, l.getProvider());
+        assertEquals(TEST_ACCURACY, l.getAccuracy(), DELTA);
+        assertEquals(TEST_VERTICAL_ACCURACY, l.getVerticalAccuracyMeters(), DELTA);
+        assertEquals(TEST_SPEED_ACCURACY, l.getSpeedAccuracyMetersPerSecond(), DELTA);
+        assertEquals(TEST_BEARING_ACCURACY, l.getBearingAccuracyDegrees(), DELTA);
+        assertEquals(TEST_ALTITUDE, l.getAltitude(), DELTA);
+        assertEquals(TEST_LATITUDE, l.getLatitude(), DELTA);
+        assertEquals(TEST_BEARING, l.getBearing(), DELTA);
+        assertEquals(TEST_LONGITUDE, l.getLongitude(), DELTA);
+        assertEquals(TEST_SPEED, l.getSpeed(), DELTA);
+        assertEquals(TEST_TIME, l.getTime());
+        assertTestBundle(l.getExtras());
+    }
+
+    private Location createTestLocation() {
+        Location l = new Location(TEST_PROVIDER);
+        l.setAccuracy(TEST_ACCURACY);
+        l.setVerticalAccuracyMeters(TEST_VERTICAL_ACCURACY);
+        l.setSpeedAccuracyMetersPerSecond(TEST_SPEED_ACCURACY);
+        l.setBearingAccuracyDegrees(TEST_BEARING_ACCURACY);
+
+        l.setAltitude(TEST_ALTITUDE);
+        l.setLatitude(TEST_LATITUDE);
+        l.setBearing(TEST_BEARING);
+        l.setLongitude(TEST_LONGITUDE);
+        l.setSpeed(TEST_SPEED);
+        l.setTime(TEST_TIME);
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(TEST_KEY1NAME, TEST_KEY1VALUE);
+        bundle.putByte(TEST_KEY2NAME, TEST_KEY2VALUE);
+        l.setExtras(bundle);
+
+        return l;
+    }
+
+    private void assertTestBundle(Bundle bundle) {
+        assertFalse(bundle.getBoolean(TEST_KEY1NAME));
+        assertEquals(TEST_KEY2VALUE, bundle.getByte(TEST_KEY2NAME));
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
index d619f32..7d150cb 100644
--- a/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
@@ -16,8 +16,6 @@
 
 package android.location.cts.none;
 
-import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
-
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
@@ -29,9 +27,6 @@
 import android.location.cts.common.LocationListenerCapture;
 import android.location.cts.common.LocationPendingIntentCapture;
 import android.os.Looper;
-import android.telephony.CellInfo;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -40,8 +35,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.List;
-
 
 @RunWith(AndroidJUnit4.class)
 public class NoLocationPermissionTest {
@@ -57,81 +50,6 @@
         assertNotNull(mLocationManager);
     }
 
-    @SuppressWarnings("deprecation")
-    @Test
-    public void testGetCellLocation() {
-        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
-            return;
-        }
-
-        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
-        assertNotNull(telephonyManager);
-
-        try {
-            telephonyManager.getCellLocation();
-            fail("Should throw SecurityException");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    @Test
-    public void testGetAllCellInfo() {
-        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
-            return;
-        }
-
-        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
-        assertNotNull(telephonyManager);
-
-        try {
-            telephonyManager.getAllCellInfo();
-            fail("Should throw SecurityException");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    @Test
-    public void testListenCellLocation() {
-        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
-            return;
-        }
-
-        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
-        assertNotNull(telephonyManager);
-
-        try {
-            telephonyManager.listen(new PhoneStateListener(Runnable::run),
-                    PhoneStateListener.LISTEN_CELL_LOCATION);
-            fail("Should throw SecurityException");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    @Test
-    public void testRequestCellInfoUpdate() {
-        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
-            return;
-        }
-
-        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
-        assertNotNull(telephonyManager);
-
-        try {
-            telephonyManager.requestCellInfoUpdate(Runnable::run,
-                    new TelephonyManager.CellInfoCallback() {
-                        @Override
-                        public void onCellInfo(List<CellInfo> cellInfos) {
-                        }
-                    });
-            fail("Should throw SecurityException");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
     @Test
     public void testRequestLocationUpdates() {
         for (String provider : mLocationManager.getAllProviders()) {
diff --git a/tests/media/src/android/mediav2/cts/MuxerTest.java b/tests/media/src/android/mediav2/cts/MuxerTest.java
index dd5f795..cecc5db 100644
--- a/tests/media/src/android/mediav2/cts/MuxerTest.java
+++ b/tests/media/src/android/mediav2/cts/MuxerTest.java
@@ -1175,16 +1175,16 @@
 
         @Test
         public void testEmptyVideoTrack() {
+            if (!mMime.startsWith("video/")) return;
             for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
-                if (!mMime.startsWith("video/")) continue;
                 if (!isMimeContainerPairValid(format)) continue;
                 if (format != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) continue;
                 try {
                     MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
                     MediaFormat mediaFormat = new MediaFormat();
                     mediaFormat.setString(MediaFormat.KEY_MIME, mMime);
-                    mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 480);
-                    mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 640);
+                    mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 96);
+                    mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 128);
                     mediaMuxer.addTrack(mediaFormat);
                     mediaMuxer.start();
                     mediaMuxer.stop();
@@ -1197,16 +1197,20 @@
 
         @Test
         public void testEmptyAudioTrack() {
+            if (!mMime.startsWith("audio/")) return;
             for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
-                if (!mMime.startsWith("audio/")) continue;
-                if (!isMimeContainerPairValid(format)) continue;
                 if (format != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) continue;
+                if (!isMimeContainerPairValid(format)) continue;
                 try {
                     MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
                     MediaFormat mediaFormat = new MediaFormat();
                     mediaFormat.setString(MediaFormat.KEY_MIME, mMime);
-                    mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 12000);
-                    mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
+                    if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
+                        mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
+                    } else {
+                        mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
+                    }
+                    mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
                     mediaMuxer.addTrack(mediaFormat);
                     mediaMuxer.start();
                     mediaMuxer.stop();
@@ -1219,8 +1223,8 @@
 
         @Test
         public void testEmptyMetaDataTrack() {
+            if (!mMime.startsWith("application/")) return;
             for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
-                if (!mMime.startsWith("application/")) continue;
                 if (!isMimeContainerPairValid(format)) continue;
                 try {
                     MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
@@ -1238,15 +1242,15 @@
 
         @Test
         public void testEmptyImageTrack() {
+            if (!mMime.startsWith("image/")) return;
             for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
-                if (!mMime.startsWith("image/")) continue;
                 if (!isMimeContainerPairValid(format)) continue;
                 try {
                     MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
                     MediaFormat mediaFormat = new MediaFormat();
                     mediaFormat.setString(MediaFormat.KEY_MIME, mMime);
-                    mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 480);
-                    mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 640);
+                    mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 96);
+                    mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 128);
                     mediaMuxer.addTrack(mediaFormat);
                     mediaMuxer.start();
                     mediaMuxer.stop();
diff --git a/tests/mocking/debuggable/TEST_MAPPING b/tests/mocking/debuggable/TEST_MAPPING
new file mode 100644
index 0000000..c6529b3
--- /dev/null
+++ b/tests/mocking/debuggable/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMockingDebuggableTestCases"
+    }
+  ]
+}
diff --git a/tests/netlegacy22.permission/AndroidManifest.xml b/tests/netlegacy22.permission/AndroidManifest.xml
index 14c40e5..85979c9 100644
--- a/tests/netlegacy22.permission/AndroidManifest.xml
+++ b/tests/netlegacy22.permission/AndroidManifest.xml
@@ -16,14 +16,15 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.netlegacy22.permission.cts">
+     package="android.netlegacy22.permission.cts">
 
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.permission.cts.PermissionStubActivity"
-                  android:label="PermissionStubActivity">
+             android:label="PermissionStubActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
@@ -32,21 +33,20 @@
     </application>
 
     <!--
-        The CTS stubs package cannot be used as the target application here,
-        since that requires many permissions to be set. Instead, specify this
-        package itself as the target and include any stub activities needed.
+                The CTS stubs package cannot be used as the target application here,
+                since that requires many permissions to be set. Instead, specify this
+                package itself as the target and include any stub activities needed.
 
-        This test package uses the default InstrumentationTestRunner, because
-        the InstrumentationCtsTestRunner is only available in the stubs
-        package. That runner cannot be added to this package either, since it
-        relies on hidden APIs.
-    -->
+                This test package uses the default InstrumentationTestRunner, because
+                the InstrumentationCtsTestRunner is only available in the stubs
+                package. That runner cannot be added to this package either, since it
+                relies on hidden APIs.
+            -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.netlegacy22.permission.cts"
-                     android:label="CTS tests of legacy android.net permissions as of API 22">
+         android:targetPackage="android.netlegacy22.permission.cts"
+         android:label="CTS tests of legacy android.net permissions as of API 22">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/netlegacy22.permission/TEST_MAPPING b/tests/netlegacy22.permission/TEST_MAPPING
new file mode 100644
index 0000000..1486eca
--- /dev/null
+++ b/tests/netlegacy22.permission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetTestCasesLegacyPermission22"
+    }
+  ]
+}
diff --git a/tests/openglperf2/AndroidManifest.xml b/tests/openglperf2/AndroidManifest.xml
index f23e411..5e7d0c1 100644
--- a/tests/openglperf2/AndroidManifest.xml
+++ b/tests/openglperf2/AndroidManifest.xml
@@ -1,53 +1,50 @@
 <?xml version="1.0" encoding="utf-8"?>
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.opengl2.cts"
-    android:versionCode="1"
-    android:versionName="1.0" >
+     package="android.opengl2.cts"
+     android:versionCode="1"
+     android:versionName="1.0">
 
-    <uses-sdk
-        android:minSdkVersion="16"
-        android:targetSdkVersion="17" />
+    <uses-sdk android:minSdkVersion="16"
+         android:targetSdkVersion="17"/>
 
-    <uses-feature
-        android:glEsVersion="0x00020000"
-        android:required="true" />
+    <uses-feature android:glEsVersion="0x00020000"
+         android:required="true"/>
 
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
-    <application android:allowBackup="false" >
-        <uses-library android:name="android.test.runner" />
+    <application android:allowBackup="false">
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name=".primitive.GLPrimitiveActivity"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
-            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+        <activity android:name=".primitive.GLPrimitiveActivity"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
 
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity
-            android:name=".reference.GLReferenceActivity"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
-            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+        <activity android:name=".reference.GLReferenceActivity"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
 
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity
-            android:name=".reference.GLGameActivity"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
-            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+        <activity android:name=".reference.GLGameActivity"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="OpenGL ES Benchmark"
-        android:targetPackage="android.opengl2.cts" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="OpenGL ES Benchmark"
+         android:targetPackage="android.opengl2.cts"/>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/quickaccesswallet/AndroidManifest.xml b/tests/quickaccesswallet/AndroidManifest.xml
index 4b6a994..e7a1df5 100755
--- a/tests/quickaccesswallet/AndroidManifest.xml
+++ b/tests/quickaccesswallet/AndroidManifest.xml
@@ -16,78 +16,76 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.quickaccesswallet.cts"
-          android:targetSandboxVersion="2">
+     package="android.quickaccesswallet.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <!-- Required for HostApduService -->
     <uses-permission android:name="android.permission.NFC"/>
     <!-- Required to test QuickAccessWalletClient feature availability -->
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.quickaccesswallet.QuickAccessWalletActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.quickaccesswallet.QuickAccessWalletActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.quickaccesswallet.QuickAccessWalletSettingsActivity">
+        <activity android:name="android.quickaccesswallet.QuickAccessWalletSettingsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name="android.quickaccesswallet.TestHostApduService"
-            android:exported="true"
-            android:permission="android.permission.BIND_NFC_SERVICE"
-            android:label="@string/app_name">
+        <service android:name="android.quickaccesswallet.TestHostApduService"
+             android:exported="true"
+             android:permission="android.permission.BIND_NFC_SERVICE"
+             android:label="@string/app_name">
             <intent-filter>
                 <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
-            <meta-data
-                android:name="android.nfc.cardemulation.host_apdu_service"
-                android:resource="@xml/hce_aids"/>
+            <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
+                 android:resource="@xml/hce_aids"/>
         </service>
 
-        <service
-            android:name="android.quickaccesswallet.TestQuickAccessWalletService"
-            android:enabled="true"
-            android:label="@string/app_name"
-            android:icon="@drawable/android"
-            android:permission="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE">
+        <service android:name="android.quickaccesswallet.TestQuickAccessWalletService"
+             android:enabled="true"
+             android:label="@string/app_name"
+             android:icon="@drawable/android"
+             android:permission="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.quickaccesswallet.QuickAccessWalletService" />
+                <action android:name="android.service.quickaccesswallet.QuickAccessWalletService"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <meta-data android:name="android.quickaccesswallet"
-                       android:resource="@xml/quickaccesswallet_configuration" />;
+                 android:resource="@xml/quickaccesswallet_configuration"/>;
         </service>
 
-        <service
-            android:name="android.quickaccesswallet.NoPermissionQuickAccessWalletService"
-            android:enabled="false"
-            android:label="@string/app_name"
-            android:icon="@drawable/android">
+        <service android:name="android.quickaccesswallet.NoPermissionQuickAccessWalletService"
+             android:enabled="false"
+             android:label="@string/app_name"
+             android:icon="@drawable/android"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.quickaccesswallet.QuickAccessWalletService" />
+                <action android:name="android.service.quickaccesswallet.QuickAccessWalletService"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <meta-data android:name="android.quickaccesswallet"
-                       android:resource="@xml/quickaccesswallet_configuration" />;
+                 android:resource="@xml/quickaccesswallet_configuration"/>;
         </service>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="Quick Access Wallet tests"
-        android:targetPackage="android.quickaccesswallet.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Quick Access Wallet tests"
+         android:targetPackage="android.quickaccesswallet.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/sample/AndroidManifest.xml b/tests/sample/AndroidManifest.xml
index adeb050..decd1bc 100755
--- a/tests/sample/AndroidManifest.xml
+++ b/tests/sample/AndroidManifest.xml
@@ -16,25 +16,24 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sample.cts"
-    android:targetSandboxVersion="2">
+     package="android.sample.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.sample.SampleDeviceActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.sample.SampleDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS sample tests"
-        android:targetPackage="android.sample.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS sample tests"
+         android:targetPackage="android.sample.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/sensor/src/android/hardware/cts/SingleSensorTests.java b/tests/sensor/src/android/hardware/cts/SingleSensorTests.java
index 1f40188..62bad39 100644
--- a/tests/sensor/src/android/hardware/cts/SingleSensorTests.java
+++ b/tests/sensor/src/android/hardware/cts/SingleSensorTests.java
@@ -174,6 +174,10 @@
         runSensorTest(Sensor.TYPE_ACCELEROMETER, RATE_1HZ);
     }
 
+    public void testAccelerometer_automotive() throws Throwable {
+        runSensorTest(Sensor.TYPE_ACCELEROMETER, RATE_25HZ, true);
+    }
+
     public void testAccelUncalibrated_fastest() throws Throwable {
         runSensorTest(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED, SensorManager.SENSOR_DELAY_FASTEST);
     }
@@ -579,12 +583,18 @@
     }
 
     private void runSensorTest(int sensorType, int rateUs) throws Throwable {
+        runSensorTest(sensorType, rateUs, false);
+    }
+
+    private void runSensorTest(int sensorType, int rateUs,
+            boolean isAutomotiveSpecificTest) throws Throwable {
         SensorCtsHelper.sleep(3, TimeUnit.SECONDS);
         TestSensorEnvironment environment = new TestSensorEnvironment(
                 getContext(),
                 sensorType,
                 shouldEmulateSensorUnderLoad(),
-                rateUs);
+                rateUs,
+                isAutomotiveSpecificTest);
         TestSensorOperation op =
                 TestSensorOperation.createOperation(environment, 5, TimeUnit.SECONDS);
         op.addDefaultVerifications();
diff --git a/tests/sensor/src/android/hardware/cts/helpers/TestSensorEnvironment.java b/tests/sensor/src/android/hardware/cts/helpers/TestSensorEnvironment.java
index 261d327..c20a0d0 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/TestSensorEnvironment.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/TestSensorEnvironment.java
@@ -44,6 +44,7 @@
     private final int mMaxReportLatencyUs;
     private final boolean mIsDeviceSuspendTest;
     private final boolean mIsIntegrationTest;
+    private final boolean mIsAutomotiveSpecificTest;
 
     /**
      * Constructs an environment for sensor testing.
@@ -112,6 +113,34 @@
      * @param sensorType The type of the sensor under test
      * @param sensorMightHaveMoreListeners Whether the sensor under test is acting under load
      * @param samplingPeriodUs The requested collection period for the sensor under test
+     * @param isAutomotiveSpecificTest Whether this is an automotive specific test
+     *
+     * @deprecated Use variants with {@link Sensor} objects.
+     */
+    @Deprecated
+    public TestSensorEnvironment(
+            Context context,
+            int sensorType,
+            boolean sensorMightHaveMoreListeners,
+            int samplingPeriodUs,
+            boolean isAutomotiveSpecificTest) {
+        this(context,
+                getSensor(context, sensorType),
+                sensorMightHaveMoreListeners,
+                samplingPeriodUs,
+                0 /* maxReportLatencyUs */,
+                false,
+                false,
+                isAutomotiveSpecificTest);
+    }
+
+    /**
+     * Constructs an environment for sensor testing.
+     *
+     * @param context The context for the test
+     * @param sensorType The type of the sensor under test
+     * @param sensorMightHaveMoreListeners Whether the sensor under test is acting under load
+     * @param samplingPeriodUs The requested collection period for the sensor under test
      * @param maxReportLatencyUs The requested collection report latency for the sensor under test
      *
      * @deprecated Use variants with {@link Sensor} objects.
@@ -216,7 +245,8 @@
                 samplingPeriodUs,
                 maxReportLatencyUs,
                 false /* isDeviceSuspendTest */,
-                isIntegrationTest);
+                isIntegrationTest,
+                false /* isAutomotiveSpecificTest */);
     }
 
     public TestSensorEnvironment(
@@ -229,7 +259,8 @@
         this(context, sensor, sensorMightHaveMoreListeners,
                 samplingPeriodUs, maxReportLatencyUs,
                 isDeviceSuspendTest,
-                false /* isIntegrationTest */);
+                false /* isIntegrationTest */,
+                false /* isAutomotiveSpecificTest */);
     }
 
     public TestSensorEnvironment(
@@ -239,7 +270,8 @@
             int samplingPeriodUs,
             int maxReportLatencyUs,
             boolean isDeviceSuspendTest,
-            boolean isIntegrationTest) {
+            boolean isIntegrationTest,
+            boolean isAutomotiveSpecificTest) {
         mContext = context;
         mSensor = sensor;
         mSensorMightHaveMoreListeners = sensorMightHaveMoreListeners;
@@ -247,6 +279,7 @@
         mMaxReportLatencyUs = maxReportLatencyUs;
         mIsDeviceSuspendTest = isDeviceSuspendTest;
         mIsIntegrationTest = isIntegrationTest;
+        mIsAutomotiveSpecificTest = isAutomotiveSpecificTest;
     }
 
     /**
@@ -459,5 +492,9 @@
     public boolean isIntegrationTest() {
         return mIsIntegrationTest;
     }
+
+    public boolean isAutomotiveSpecificTest() {
+        return mIsAutomotiveSpecificTest;
+    }
 }
 
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanVerification.java b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanVerification.java
index 7a48ba8..c66eb30 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanVerification.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanVerification.java
@@ -70,10 +70,16 @@
         Map<Integer, ExpectedValuesAndThresholds> currentDefaults =
                 new HashMap<Integer, ExpectedValuesAndThresholds>(DEFAULTS);
 
-        // For automotive flag, add car default tests.
-        if(environment.getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE)) {
-            addCarDefaultTests(currentDefaults);
+        // Handle automotive specific tests.
+        if(environment.isAutomotiveSpecificTest()) {
+            // If device is an automotive device, add car defaults.
+            if (environment.getContext().getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_AUTOMOTIVE)) {
+                addCarDefaultTests(currentDefaults);
+            } else {
+                // Skip as this is an automotive test and device is non-automotive.
+                return null;
+            }
         }
 
         int sensorType = environment.getSensor().getType();
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
index 7e8c6d6..5229a70 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
@@ -90,16 +90,7 @@
             mResultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, e.getClass().getName(),
                     writer.toString());
         }
-        if (mResultObserver.mDidFail) {
-            StringBuilder errorString = mResultObserver.mErrorString;
-            ClassLoader classLoader = getClass().getClassLoader();
-            errorString.append("\nClassLoader hierarchy\n");
-            while (classLoader != null) {
-                errorString.append("    ").append(classLoader).append("\n");
-                classLoader = classLoader.getParent();
-            }
-            fail(errorString.toString());
-        }
+        mResultObserver.onTestComplete(); // Will throw is there are failures
     }
 
     static String[] getCommaSeparatedList(Bundle instrumentationArgs, String key) {
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java b/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java
index 715cf2f..5b74951 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java
@@ -19,15 +19,18 @@
 import android.signature.cts.FailureType;
 import android.signature.cts.ResultObserver;
 
+import repackaged.junit.framework.Assert;
+import repackaged.junit.framework.TestCase;
+
 /**
  * Keeps track of any reported failures.
  */
 class TestResultObserver implements ResultObserver {
 
-    boolean mDidFail = false;
-    int failures = 0;
+    private boolean mDidFail = false;
+    private int failures = 0;
 
-    StringBuilder mErrorString = new StringBuilder();
+    private StringBuilder mErrorString = new StringBuilder();
 
     @Override
     public void notifyFailure(FailureType type, String name, String errorMessage) {
@@ -41,7 +44,26 @@
             mErrorString.append("\tError: ");
             mErrorString.append(errorMessage);
         } else if (failures == 101) {
-            mErrorString.append("\nMore than 100 failures, more errors will be elided.");
+            mErrorString.append("\nMore than 100 failures, aborting test.");
+            finalizeErrorString();
+            Assert.fail(mErrorString.toString());
         }
     }
+
+    private void finalizeErrorString() {
+        ClassLoader classLoader = getClass().getClassLoader();
+        mErrorString.append("\nClassLoader hierarchy\n");
+        while (classLoader != null) {
+            mErrorString.append("    ").append(classLoader).append("\n");
+            classLoader = classLoader.getParent();
+        }
+    }
+
+    public void onTestComplete() {
+        if (mDidFail) {
+            finalizeErrorString();
+            Assert.fail(mErrorString.toString());
+        }
+
+    }
 }
diff --git a/tests/signature/intent-check/TEST_MAPPING b/tests/signature/intent-check/TEST_MAPPING
new file mode 100644
index 0000000..e731491
--- /dev/null
+++ b/tests/signature/intent-check/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsIntentSignatureTestCases"
+    }
+  ]
+}
diff --git a/tests/suspendapps/test-apps/TestDeviceAdmin/AndroidManifest.xml b/tests/suspendapps/test-apps/TestDeviceAdmin/AndroidManifest.xml
index 3368398..b336cfb 100644
--- a/tests/suspendapps/test-apps/TestDeviceAdmin/AndroidManifest.xml
+++ b/tests/suspendapps/test-apps/TestDeviceAdmin/AndroidManifest.xml
@@ -15,21 +15,20 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.suspendapps.testdeviceadmin" >
+     package="com.android.suspendapps.testdeviceadmin">
 
-    <application android:label="CTS Device Admin" android:testOnly="true">
-        <receiver
-            android:name=".TestDeviceAdmin"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
-            <meta-data
-                android:name="android.app.device_admin"
-                android:resource="@xml/device_admin"/>
+    <application android:label="CTS Device Admin"
+         android:testOnly="true">
+        <receiver android:name=".TestDeviceAdmin"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
+            <meta-data android:name="android.app.device_admin"
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <receiver
-            android:name=".TestCommsReceiver"
-            android:exported="true" />
+        <receiver android:name=".TestCommsReceiver"
+             android:exported="true"/>
     </application>
 </manifest>
diff --git a/tests/suspendapps/tests/AndroidManifest.xml b/tests/suspendapps/tests/AndroidManifest.xml
index 61dd2f2..2ea98af 100755
--- a/tests/suspendapps/tests/AndroidManifest.xml
+++ b/tests/suspendapps/tests/AndroidManifest.xml
@@ -15,27 +15,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.suspendapps.cts">
+     package="android.suspendapps.cts">
 
     <application android:label="CTS Suspend Apps Test">
         <activity android:name=".SuspendedDetailsActivity"
-                  android:permission="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS">
+             android:permission="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SHOW_SUSPENDED_APP_DETAILS" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.SHOW_SUSPENDED_APP_DETAILS"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <receiver android:name=".UnsuspendReceiver">
+        <receiver android:name=".UnsuspendReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY" />
+                <action android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY"/>
             </intent-filter>
         </receiver>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:functionalTest="true"
-                     android:targetPackage="android.suspendapps.cts"
-                     android:label="CTS Suspend Apps Test"/>
+         android:functionalTest="true"
+         android:targetPackage="android.suspendapps.cts"
+         android:label="CTS Suspend Apps Test"/>
 </manifest>
diff --git a/tests/tests/accounts/AndroidManifest.xml b/tests/tests/accounts/AndroidManifest.xml
index a31b77a..999a3c6 100644
--- a/tests/tests/accounts/AndroidManifest.xml
+++ b/tests/tests/accounts/AndroidManifest.xml
@@ -16,59 +16,61 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.accounts.cts"
-        android:targetSandboxVersion="2">
+     package="android.accounts.cts"
+     android:targetSandboxVersion="2">
     <uses-sdk android:minSdkVersion="1"
-          android:targetSdkVersion="26"/>
+         android:targetSdkVersion="26"/>
 
     <!-- Don't need GET_ACCOUNTS because share a Uid with the relevant
-         authenticators -->
+                 authenticators -->
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="android.accounts.cts.AccountDummyActivity" >
+        <activity android:name="android.accounts.cts.AccountDummyActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.accounts.cts.AccountRemovalDummyActivity" >
+        <activity android:name="android.accounts.cts.AccountRemovalDummyActivity">
         </activity>
 
-        <activity android:name="android.accounts.cts.AccountAuthenticatorDummyActivity" />
+        <activity android:name="android.accounts.cts.AccountAuthenticatorDummyActivity"/>
 
-        <service android:name="MockAccountService" android:exported="true"
-                 android:process="android.accounts.cts">
+        <service android:name="MockAccountService"
+             android:exported="true"
+             android:process="android.accounts.cts">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
             <meta-data android:name="android.accounts.AccountAuthenticator"
-                       android:resource="@xml/authenticator" />
+                 android:resource="@xml/authenticator"/>
         </service>
 
-        <service android:name="MockCustomTokenAccountService" android:exported="true"
-                 android:process="android.accounts.cts">
+        <service android:name="MockCustomTokenAccountService"
+             android:exported="true"
+             android:process="android.accounts.cts">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
             <meta-data android:name="android.accounts.AccountAuthenticator"
-                       android:resource="@xml/custom_token_authenticator" />
+                 android:resource="@xml/custom_token_authenticator"/>
             <meta-data android:name="android.accounts.AccountAuthenticator.customTokens"
-                       android:value="1" />
+                 android:value="1"/>
 
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.accounts.cts"
-                     android:label="CTS tests for android.accounts">
+         android:targetPackage="android.accounts.cts"
+         android:label="CTS tests for android.accounts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/appcomponentfactory/TEST_MAPPING b/tests/tests/appcomponentfactory/TEST_MAPPING
new file mode 100644
index 0000000..a69b8bc
--- /dev/null
+++ b/tests/tests/appcomponentfactory/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppComponentFactoryTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
index 2eba524..6641c9a 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
index 564f712..f8fcea4 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
index 1e94c1b..d046e70 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
index 0126775..1d5f0bc 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
index e98a98c..07b2be6 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
index 2810c87..60a0c2d 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
index ce56a77..b5a88a8 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
index d63d1d5..d06e632 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
index 87f8ab7..2f1cf69 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
index 1554de8..7fb4191 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
index b451455..dab3e2e 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
index cde61df..aabb703 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
index 3ec5b39..72dfa6b 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
index 4562040..09755f0 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
index 26ef435..d1fdd13 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
index 8cf6bfe..b08cc12 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
index a51d7f4..80138c5 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
index 1bfa17e..0f7e971 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
index 57efc78..e9a051b 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
index 3355c35..74412cc 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
index ffd8848..64e3af3 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
index 696ef30..f4aecba 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
@@ -19,7 +19,8 @@
     package="android.appenumeration.browser.activity">
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.WebBrowser">
+        <activity android:name="android.appenumeration.WebBrowser"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
index 60ced57..97b0d9b 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
@@ -19,7 +19,8 @@
     package="android.appenumeration.browser.wildcard.activity">
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.WebBrowser">
+        <activity android:name="android.appenumeration.WebBrowser"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
index e31d018..0bff9cc 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
index 445f90b..6795cf7 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
@@ -1,25 +1,26 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.appenumeration.editor.activity">
+     package="android.appenumeration.editor.activity">
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.EditorActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.appenumeration.EditorActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW"/>
                 <action android:name="android.intent.action.EDIT"/>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
index 90b7c6d..59ec446 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
@@ -1,74 +1,77 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.appenumeration.filters">
+     package="android.appenumeration.filters">
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.appenumeration.testapp.DummyActivity"
-                  android:visibleToInstantApps="true">
+             android:visibleToInstantApps="true"
+             android:exported="true">
             <!-- Marked visible to instant apps to ensure this logic doesn't conflict with non
-                 instant filtering -->
+                                 instant filtering -->
             <intent-filter>
-                <action android:name="android.appenumeration.action.ACTIVITY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.appenumeration.action.ACTIVITY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <service android:name="android.appenumeration.testapp.DummyService">
+        <service android:name="android.appenumeration.testapp.DummyService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appenumeration.action.SERVICE" />
+                <action android:name="android.appenumeration.action.SERVICE"/>
             </intent-filter>
         </service>
         <provider android:name="android.appenumeration.testapp.DummyProvider"
-                  android:authorities="android.appenumeration.testapp"
-                  android:exported="true">
+             android:authorities="android.appenumeration.testapp"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appenumeration.action.PROVIDER" />
+                <action android:name="android.appenumeration.action.PROVIDER"/>
             </intent-filter>
         </provider>
-        <receiver android:name="android.appenumeration.testapp.DummyReceiver">
+        <receiver android:name="android.appenumeration.testapp.DummyReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appenumeration.action.BROADCAST" />
+                <action android:name="android.appenumeration.action.BROADCAST"/>
             </intent-filter>
         </receiver>
 
         <activity android:name="android.appenumeration.testapp.DummyActivityNotExported"
-                  android:exported="false">
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.appenumeration.action.ACTIVITY_UNEXPORTED" />
+                <action android:name="android.appenumeration.action.ACTIVITY_UNEXPORTED"/>
             </intent-filter>
         </activity>
         <service android:name="android.appenumeration.testapp.DummyServiceNotExported"
-                 android:exported="false">
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.appenumeration.action.SERVICE_UNEXPORTED" />
+                <action android:name="android.appenumeration.action.SERVICE_UNEXPORTED"/>
             </intent-filter>
         </service>
         <provider android:name="android.appenumeration.testapp.DummyProviderNotExported"
-                  android:authorities="android.appenumeration.testapp.unexported"
-                  android:exported="false" >
+             android:authorities="android.appenumeration.testapp.unexported"
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.appenumeration.action.PROVIDER_UNEXPORTED" />
+                <action android:name="android.appenumeration.action.PROVIDER_UNEXPORTED"/>
             </intent-filter>
         </provider>
         <receiver android:name="android.appenumeration.testapp.DummyReceiverNotExported"
-                  android:exported="false">
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.appenumeration.action.BROADCAST_UNEXPORTED" />
+                <action android:name="android.appenumeration.action.BROADCAST_UNEXPORTED"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
index 041d350..3778b04 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
index c3d8487..3b5be22 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
index 9b25acc..fc2835e 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
index 87f621a..148fa29 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
@@ -1,25 +1,26 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.appenumeration.share.activity">
+     package="android.appenumeration.share.activity">
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.ShareActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.appenumeration.ShareActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
index e198ea5..31fe275 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
@@ -1,31 +1,34 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.appenumeration.web.activity">
+     package="android.appenumeration.web.activity">
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.WebActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.appenumeration.WebActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW"/>
                 <category android:name="android.intent.category.BROWSABLE"/>
                 <category android:name="android.intent.category.DEFAULT"/>
-                <data android:scheme="http" android:host="appenumeration.android"/>
-                <data android:scheme="https" android:host="appenumeration.android"/>
+                <data android:scheme="http"
+                     android:host="appenumeration.android"/>
+                <data android:scheme="https"
+                     android:host="appenumeration.android"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/appop/Android.bp b/tests/tests/appop/Android.bp
index 4779f43..4d3bc67 100644
--- a/tests/tests/appop/Android.bp
+++ b/tests/tests/appop/Android.bp
@@ -92,5 +92,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/appop/AndroidManifest.xml b/tests/tests/appop/AndroidManifest.xml
index 0c89b6e..065b0af 100644
--- a/tests/tests/appop/AndroidManifest.xml
+++ b/tests/tests/appop/AndroidManifest.xml
@@ -28,6 +28,7 @@
   <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
   <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
   <uses-permission android:name="android.permission.BLUETOOTH" />
+  <uses-permission android:name="android.permission.READ_LOGS" />
 
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
   <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
@@ -41,11 +42,26 @@
 
   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 
+  <uses-permission android:name="android.permission.SEND_SMS" />
+
   <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
   <application>
       <uses-library android:name="android.test.runner"/>
       <activity android:name=".UidStateForceActivity" />
+      <receiver android:name=".PublicActionReceiver"
+                android:exported="false">
+          <intent-filter>
+              <action android:name="android.app.appops.cts.PUBLIC_ACTION" />
+          </intent-filter>
+      </receiver>
+      <receiver android:name=".ProtectedActionReceiver"
+                android:exported="false"
+                android:permission="android.permission.READ_CONTACTS">
+          <intent-filter>
+              <action android:name="android.app.appops.cts.PROTECTED_ACTION" />
+          </intent-filter>
+      </receiver>
   </application>
 
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/appop/AndroidTest.xml b/tests/tests/appop/AndroidTest.xml
index 29f01e0..fc5c0bf 100644
--- a/tests/tests/appop/AndroidTest.xml
+++ b/tests/tests/appop/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/appop/AppInBackground/Android.bp b/tests/tests/appop/AppInBackground/Android.bp
index baa001a..b27fa1f 100644
--- a/tests/tests/appop/AppInBackground/Android.bp
+++ b/tests/tests/appop/AppInBackground/Android.bp
@@ -18,5 +18,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
diff --git a/tests/tests/appop/AppThatCanBeForcedIntoForegroundStates/Android.bp b/tests/tests/appop/AppThatCanBeForcedIntoForegroundStates/Android.bp
index fb06b6e..f773646 100644
--- a/tests/tests/appop/AppThatCanBeForcedIntoForegroundStates/Android.bp
+++ b/tests/tests/appop/AppThatCanBeForcedIntoForegroundStates/Android.bp
@@ -24,5 +24,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
\ No newline at end of file
diff --git a/tests/tests/appop/AppThatUsesAppOps/Android.bp b/tests/tests/appop/AppThatUsesAppOps/Android.bp
index d4b546c..20387bd 100644
--- a/tests/tests/appop/AppThatUsesAppOps/Android.bp
+++ b/tests/tests/appop/AppThatUsesAppOps/Android.bp
@@ -58,5 +58,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
\ No newline at end of file
diff --git a/tests/tests/appop/AppToBlame1/Android.bp b/tests/tests/appop/AppToBlame1/Android.bp
index 9867a6a..b8f480e 100644
--- a/tests/tests/appop/AppToBlame1/Android.bp
+++ b/tests/tests/appop/AppToBlame1/Android.bp
@@ -18,5 +18,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
\ No newline at end of file
diff --git a/tests/tests/appop/AppToBlame2/Android.bp b/tests/tests/appop/AppToBlame2/Android.bp
index e11008c..d463dd1 100644
--- a/tests/tests/appop/AppToBlame2/Android.bp
+++ b/tests/tests/appop/AppToBlame2/Android.bp
@@ -18,5 +18,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
\ No newline at end of file
diff --git a/tests/tests/appop/AppToCollect/Android.bp b/tests/tests/appop/AppToCollect/Android.bp
index 1706aea..b98c4c1 100644
--- a/tests/tests/appop/AppToCollect/Android.bp
+++ b/tests/tests/appop/AppToCollect/Android.bp
@@ -18,5 +18,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithAttributionInheritingFromExisting/Android.bp b/tests/tests/appop/AppWithAttributionInheritingFromExisting/Android.bp
index 10f916a..4d2afd9 100644
--- a/tests/tests/appop/AppWithAttributionInheritingFromExisting/Android.bp
+++ b/tests/tests/appop/AppWithAttributionInheritingFromExisting/Android.bp
@@ -18,5 +18,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithAttributionInheritingFromSameAsOther/Android.bp b/tests/tests/appop/AppWithAttributionInheritingFromSameAsOther/Android.bp
index 2214391..d21f722 100644
--- a/tests/tests/appop/AppWithAttributionInheritingFromSameAsOther/Android.bp
+++ b/tests/tests/appop/AppWithAttributionInheritingFromSameAsOther/Android.bp
@@ -18,5 +18,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithAttributionInheritingFromSelf/Android.bp b/tests/tests/appop/AppWithAttributionInheritingFromSelf/Android.bp
index 2317c70..fe3af04 100644
--- a/tests/tests/appop/AppWithAttributionInheritingFromSelf/Android.bp
+++ b/tests/tests/appop/AppWithAttributionInheritingFromSelf/Android.bp
@@ -18,5 +18,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithDuplicateAttribution/Android.bp b/tests/tests/appop/AppWithDuplicateAttribution/Android.bp
index f1e7068..a9b119d 100644
--- a/tests/tests/appop/AppWithDuplicateAttribution/Android.bp
+++ b/tests/tests/appop/AppWithDuplicateAttribution/Android.bp
@@ -18,5 +18,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithLongAttributionTag/Android.bp b/tests/tests/appop/AppWithLongAttributionTag/Android.bp
index f5afd4e..5f6f673 100644
--- a/tests/tests/appop/AppWithLongAttributionTag/Android.bp
+++ b/tests/tests/appop/AppWithLongAttributionTag/Android.bp
@@ -18,5 +18,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithTooManyAttributions/Android.bp b/tests/tests/appop/AppWithTooManyAttributions/Android.bp
index d681b21..0af244f 100644
--- a/tests/tests/appop/AppWithTooManyAttributions/Android.bp
+++ b/tests/tests/appop/AppWithTooManyAttributions/Android.bp
@@ -18,5 +18,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
\ No newline at end of file
diff --git a/tests/tests/appop/TEST_MAPPING b/tests/tests/appop/TEST_MAPPING
new file mode 100644
index 0000000..42315bd
--- /dev/null
+++ b/tests/tests/appop/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppOpsTestCases"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
index b1588d5..44e4c05 100644
--- a/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
+++ b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
@@ -175,4 +175,16 @@
         InstrumentationRegistry.getInstrumentation().targetContext
                 .getSystemService(AppOpsManager::class.java).getOpsForPackage(uid, packageName, op)
     }[0].ops[0]
+}
+
+/**
+ * Run a block with a compat change disabled
+ */
+fun withDisabledCompatChange(changeId: Long, packageName: String, wrapped: () -> Unit) {
+    runCommand("am compat disable $changeId $packageName")
+    try {
+        wrapped()
+    } finally {
+        runCommand("am compat reset $changeId $packageName")
+    }
 }
\ No newline at end of file
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt
index efb863b..8740478 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt
@@ -19,6 +19,7 @@
 import android.app.AppOpsManager
 import android.app.AppOpsManager.MAX_PRIORITY_UID_STATE
 import android.app.AppOpsManager.MIN_PRIORITY_UID_STATE
+import android.app.AppOpsManager.MODE_ALLOWED
 import android.app.AppOpsManager.OPSTR_WIFI_SCAN
 import android.app.AppOpsManager.OP_FLAGS_ALL
 import android.app.AppOpsManager.OP_FLAG_SELF
@@ -50,6 +51,23 @@
 
     private val myUid = android.os.Process.myUid()
     private val myPackage = context.packageName
+    private val otherPkg: String
+    private val otherUid: Int
+    private val firstTag = "firstProxyAttribution"
+    private val secondTag = "secondProxyAttribution"
+
+    init {
+    // Find another app to blame
+    val otherAppInfo = context.packageManager
+        .resolveActivity(Intent(ACTION_INSTALL_PACKAGE).addCategory(Intent.CATEGORY_DEFAULT)
+            .setDataAndType(Uri.parse("content://com.example/foo.apk"),
+                "application/vnd.android.package-archive"), 0)
+        ?.activityInfo?.applicationInfo
+
+        assumeNotNull(otherAppInfo)
+        otherPkg = otherAppInfo!!.packageName
+        otherUid = otherAppInfo.uid
+    }
 
     // Start an activity to make sure this app counts as being in the foreground
     @Rule
@@ -87,8 +105,8 @@
         sleep(1)
 
         assertThat(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!
-                .getLastAccessTime(MAX_PRIORITY_UID_STATE, UID_STATE_TOP, OP_FLAGS_ALL))
-                .isIn(before..beforeUidChange)
+            .getLastAccessTime(MAX_PRIORITY_UID_STATE, UID_STATE_TOP, OP_FLAGS_ALL))
+            .isIn(before..beforeUidChange)
 
         try {
             activityRule.activity.finish()
@@ -97,7 +115,7 @@
             eventually {
                 // The system remembers the time before and after the uid change as separate events
                 assertThat(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!
-                        .getLastAccessTime(UID_STATE_TOP + 1, MIN_PRIORITY_UID_STATE,
+                    .getLastAccessTime(UID_STATE_TOP + 1, MIN_PRIORITY_UID_STATE,
                         OP_FLAGS_ALL)).isAtLeast(beforeUidChange)
             }
         } finally {
@@ -220,7 +238,7 @@
         val attributionOpEntry = opEntry.attributedOpEntries[null]!!
 
         assertThat(attributionOpEntry.getLastAccessTime(OP_FLAG_TRUSTED_PROXY))
-                .isIn(before..afterTrusted)
+            .isIn(before..afterTrusted)
         assertThat(attributionOpEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(afterTrusted..after)
         assertThat(opEntry.getLastAccessTime(OP_FLAG_TRUSTED_PROXY)).isIn(before..afterTrusted)
         assertThat(opEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(afterTrusted..after)
@@ -233,19 +251,19 @@
     @Test
     fun noteForTwoAttributionsCheckOpEntries() {
         val before = System.currentTimeMillis()
-        appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, "firstAttribution", null)
+        appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, firstTag, null)
         val afterFirst = System.currentTimeMillis()
 
         // Make sure timestamps are distinct
         sleep(1)
 
         // self note
-        appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, "secondAttribution", null)
+        appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, secondTag, null)
         val after = System.currentTimeMillis()
 
         val opEntry = getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!
-        val firstAttributionOpEntry = opEntry.attributedOpEntries["firstAttribution"]!!
-        val secondAttributionOpEntry = opEntry.attributedOpEntries["secondAttribution"]!!
+        val firstAttributionOpEntry = opEntry.attributedOpEntries[firstTag]!!
+        val secondAttributionOpEntry = opEntry.attributedOpEntries[secondTag]!!
 
         assertThat(firstAttributionOpEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(before..afterFirst)
         assertThat(secondAttributionOpEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(afterFirst..after)
@@ -257,60 +275,187 @@
     @AppModeFull(reason = "instant apps cannot see other packages")
     @Test
     fun noteFromTwoProxiesAndVerifyProxyInfo() {
-        // Find another app to blame
-        val otherAppInfo = context.packageManager
-                .resolveActivity(Intent(ACTION_INSTALL_PACKAGE).addCategory(Intent.CATEGORY_DEFAULT)
-                        .setDataAndType(Uri.parse("content://com.example/foo.apk"),
-                                "application/vnd.android.package-archive"), 0)
-                ?.activityInfo?.applicationInfo
-
-        assumeNotNull(otherAppInfo)
-
-        val otherPkg = otherAppInfo!!.packageName
-        val otherUid = otherAppInfo.uid
-
         // Using the shell identity causes a trusted proxy note
         runWithShellPermissionIdentity {
-            context.createAttributionContext("firstProxyAttribution")
-                    .getSystemService(AppOpsManager::class.java)
-                    .noteProxyOp(OPSTR_WIFI_SCAN, otherPkg, otherUid, null, null)
+            context.createAttributionContext(firstTag)
+                .getSystemService(AppOpsManager::class.java)
+                .noteProxyOp(OPSTR_WIFI_SCAN, otherPkg, otherUid, null, null)
         }
 
         // Make sure timestamps are distinct
         sleep(1)
 
         // untrusted proxy note
-        context.createAttributionContext("secondProxyAttribution")
-                .getSystemService(AppOpsManager::class.java)
-                .noteProxyOp(OPSTR_WIFI_SCAN, otherPkg, otherUid, null, null)
+        context.createAttributionContext(secondTag)
+            .getSystemService(AppOpsManager::class.java)
+            .noteProxyOp(OPSTR_WIFI_SCAN, otherPkg, otherUid, null, null)
 
         val opEntry = getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!
         val attributionOpEntry = opEntry.attributedOpEntries[null]!!
 
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.packageName)
-                .isEqualTo(myPackage)
+            .isEqualTo(myPackage)
         assertThat(opEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.packageName)
-                .isEqualTo(myPackage)
+            .isEqualTo(myPackage)
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.uid)
-                .isEqualTo(myUid)
+            .isEqualTo(myUid)
         assertThat(opEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.uid).isEqualTo(myUid)
 
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.packageName)
-                .isEqualTo(myPackage)
+            .isEqualTo(myPackage)
         assertThat(opEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.packageName)
-                .isEqualTo(myPackage)
+            .isEqualTo(myPackage)
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.uid)
-                .isEqualTo(myUid)
+            .isEqualTo(myUid)
         assertThat(opEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.uid).isEqualTo(myUid)
 
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.attributionTag)
-                .isEqualTo("firstProxyAttribution")
+            .isEqualTo(firstTag)
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.attributionTag)
-                .isEqualTo("secondProxyAttribution")
+            .isEqualTo(secondTag)
 
         // If asked for all op-flags the second attribution overrides the first
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAGS_ALL)?.attributionTag)
-                .isEqualTo("secondProxyAttribution")
+            .isEqualTo(secondTag)
+    }
+
+    @AppModeFull(reason = "instant apps cannot see other packages")
+    @Test
+    fun startStopTrustedProxyVerifyRunningAndTime() {
+        val beforeTrusted = System.currentTimeMillis()
+        // Make sure timestamps are distinct
+        sleep(1)
+
+        lateinit var firstAttrManager: AppOpsManager
+        // Using the shell identity causes a trusted proxy op
+        runWithShellPermissionIdentity {
+            firstAttrManager = context.createAttributionContext(firstTag)!!
+                .getSystemService(AppOpsManager::class.java)!!
+            val start = firstAttrManager.startProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null,
+                null)
+            assertThat(start).isEqualTo(MODE_ALLOWED)
+            sleep(1)
+        }
+
+        with(getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!) {
+            assertThat(attributedOpEntries[null]!!.isRunning).isTrue()
+            assertThat(attributedOpEntries[null]?.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)!!
+                .packageName).isEqualTo(myPackage)
+            assertThat(attributedOpEntries[null]?.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)!!
+                .attributionTag).isEqualTo(firstTag)
+            assertThat(isRunning).isTrue()
+        }
+
+        with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
+            assertThat(attributedOpEntries[firstTag]!!.isRunning).isTrue()
+            assertThat(attributedOpEntries[firstTag]!!
+                .getLastProxyInfo(OP_FLAGS_ALL)).isNull()
+        }
+
+        firstAttrManager.finishProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null)
+        sleep(1)
+        val afterTrusted = System.currentTimeMillis()
+
+        val opEntry = getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!
+        val attributionOpEntry = opEntry.attributedOpEntries[null]!!
+        assertThat(attributionOpEntry.isRunning).isFalse()
+        assertThat(opEntry.isRunning).isFalse()
+        assertThat(attributionOpEntry.getLastAccessTime(OP_FLAG_TRUSTED_PROXIED))
+            .isIn(beforeTrusted..afterTrusted)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.packageName)
+            .isEqualTo(myPackage)
+        assertThat(opEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.packageName)
+            .isEqualTo(myPackage)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.uid)
+            .isEqualTo(myUid)
+        assertThat(opEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.uid).isEqualTo(myUid)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.attributionTag)
+            .isEqualTo(firstTag)
+    }
+
+    @AppModeFull(reason = "instant apps cannot see other packages")
+    @Test
+    fun startStopUntrustedProxyVerifyRunningAndTime() {
+        val beforeUntrusted = System.currentTimeMillis()
+        // Make sure timestamps are distinct
+        sleep(1)
+
+        // Untrusted proxy op
+        val secondAttrManager = context.createAttributionContext(secondTag)!!
+            .getSystemService(AppOpsManager::class.java)!!
+        secondAttrManager.startProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null, null)
+        with(getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!) {
+            assertThat(attributedOpEntries[null]?.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)!!
+                .packageName).isEqualTo(myPackage)
+            assertThat(attributedOpEntries[null]?.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)!!
+                .attributionTag).isEqualTo(secondTag)
+        }
+
+        with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
+            assertThat(attributedOpEntries[secondTag]!!.isRunning).isTrue()
+            assertThat(attributedOpEntries[secondTag]!!
+                .getLastProxyInfo(OP_FLAGS_ALL)).isNull()
+        }
+
+        secondAttrManager.finishProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null)
+        sleep(1)
+        val afterUntrusted = System.currentTimeMillis()
+
+        val opEntry = getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!
+        val attributionOpEntry = opEntry.attributedOpEntries[null]!!
+
+        assertThat(attributionOpEntry.isRunning).isFalse()
+        assertThat(opEntry.isRunning).isFalse()
+        assertThat(attributionOpEntry.getLastAccessTime(OP_FLAG_UNTRUSTED_PROXIED))
+            .isIn(beforeUntrusted..afterUntrusted)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.packageName)
+            .isEqualTo(myPackage)
+        assertThat(opEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.packageName)
+            .isEqualTo(myPackage)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.uid)
+            .isEqualTo(myUid)
+        assertThat(opEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.uid).isEqualTo(myUid)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.attributionTag)
+            .isEqualTo(secondTag)
+    }
+
+    @AppModeFull(reason = "instant apps cannot see other packages")
+    @Test
+    fun startStopTrustedAndUntrustedProxyVerifyProxyInfo() {
+        lateinit var firstAttrManager: AppOpsManager
+        // Using the shell identity causes a trusted proxy op
+        runWithShellPermissionIdentity {
+            firstAttrManager = context.createAttributionContext(firstTag)!!
+                .getSystemService(AppOpsManager::class.java)!!
+            val start = firstAttrManager.startProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null,
+                null)
+            sleep(1)
+        }
+
+        firstAttrManager.finishProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null)
+        sleep(1)
+
+        // Untrusted proxy op
+        val secondAttrManager = context.createAttributionContext(secondTag)!!
+            .getSystemService(AppOpsManager::class.java)!!
+        secondAttrManager.startProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null, null)
+
+        sleep(1)
+        secondAttrManager.finishProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null)
+
+        val opEntry = getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!
+        val attributionOpEntry = opEntry.attributedOpEntries[null]!!
+        assertThat(attributionOpEntry.isRunning).isFalse()
+        assertThat(opEntry.isRunning).isFalse()
+
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.attributionTag)
+            .isEqualTo(firstTag)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.attributionTag)
+            .isEqualTo(secondTag)
+
+        // If asked for all op-flags the second attribution overrides the first
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAGS_ALL)?.attributionTag)
+            .isEqualTo(secondTag)
     }
 
     @Test
@@ -372,12 +517,12 @@
 
         with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
             assertThat(attributedOpEntries[null]!!.getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeNullAttributionStart..afterNullAttributionStart)
+                .isIn(beforeNullAttributionStart..afterNullAttributionStart)
             attributedOpEntries[TEST_ATTRIBUTION_TAG]?.let {
                 assertThat(it.getLastAccessTime(OP_FLAGS_ALL)).isAtMost(beforeNullAttributionStart)
             }
             assertThat(getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeNullAttributionStart..afterNullAttributionStart)
+                .isIn(beforeNullAttributionStart..afterNullAttributionStart)
         }
 
         val beforeFirstAttributionStart = System.currentTimeMillis()
@@ -386,11 +531,11 @@
 
         with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
             assertThat(attributedOpEntries[null]!!.getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeNullAttributionStart..afterNullAttributionStart)
+                .isIn(beforeNullAttributionStart..afterNullAttributionStart)
             assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!.getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
+                .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
             assertThat(getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
+                .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
         }
 
         appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, TEST_ATTRIBUTION_TAG, null)
@@ -398,11 +543,11 @@
         // Nested startOps do _not_ count as another access
         with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
             assertThat(attributedOpEntries[null]!!.getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeNullAttributionStart..afterNullAttributionStart)
+                .isIn(beforeNullAttributionStart..afterNullAttributionStart)
             assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!.getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
+                .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
             assertThat(getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
+                .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
         }
 
         appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, TEST_ATTRIBUTION_TAG)
@@ -412,9 +557,9 @@
 
     @Test
     fun startStopMultipleOpsAndVerifyDuration() {
-        val beforeNullAttributionStart = SystemClock.elapsedRealtime()
+        val beforeNullAttrStart = SystemClock.elapsedRealtime()
         appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, null, null)
-        val afterNullAttributionStart = SystemClock.elapsedRealtime()
+        val afterNullAttrStart = SystemClock.elapsedRealtime()
 
         run {
             val beforeGetOp = SystemClock.elapsedRealtime()
@@ -422,17 +567,15 @@
                 val afterGetOp = SystemClock.elapsedRealtime()
 
                 assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
                 assertThat(getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
             }
         }
 
-        val beforeAttributionStart = SystemClock.elapsedRealtime()
+        val beforeAttrStart = SystemClock.elapsedRealtime()
         appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, TEST_ATTRIBUTION_TAG, null)
-        val afterAttributionStart = SystemClock.elapsedRealtime()
+        val afterAttrStart = SystemClock.elapsedRealtime()
 
         run {
             val beforeGetOp = SystemClock.elapsedRealtime()
@@ -440,15 +583,14 @@
                 val afterGetOp = SystemClock.elapsedRealtime()
 
                 assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
                 assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!
-                        .getLastDuration(OP_FLAGS_ALL)).isIn(beforeGetOp -
-                                afterAttributionStart..afterGetOp - beforeAttributionStart)
+                    .getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
 
                 // The last duration is the duration of the last started attribution
-                assertThat(getLastDuration(OP_FLAGS_ALL)).isIn(beforeGetOp -
-                        afterAttributionStart..afterGetOp - beforeAttributionStart)
+                assertThat(getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
             }
         }
 
@@ -462,14 +604,12 @@
                 val afterGetOp = SystemClock.elapsedRealtime()
 
                 assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
                 assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!
-                        .getLastDuration(OP_FLAGS_ALL)).isIn(beforeGetOp -
-                        afterAttributionStart..afterGetOp - beforeAttributionStart)
+                    .getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
                 assertThat(getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp -
-                                afterAttributionStart..afterGetOp - beforeAttributionStart)
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
             }
         }
 
@@ -481,19 +621,19 @@
                 val afterGetOp = SystemClock.elapsedRealtime()
 
                 assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
                 assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!
-                        .getLastDuration(OP_FLAGS_ALL)).isIn(beforeGetOp -
-                                afterAttributionStart..afterGetOp - beforeAttributionStart)
-                assertThat(getLastDuration(OP_FLAGS_ALL)).isIn(beforeGetOp -
-                                afterAttributionStart..afterGetOp - beforeAttributionStart)
+                    .getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
+                assertThat(getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
             }
         }
 
-        val beforeAttributionStop = SystemClock.elapsedRealtime()
+        val beforeAttrStop = SystemClock.elapsedRealtime()
         appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, TEST_ATTRIBUTION_TAG)
-        val afterAttributionStop = SystemClock.elapsedRealtime()
+        sleep(1)
+        val afterAttrStop = SystemClock.elapsedRealtime()
 
         run {
             val beforeGetOp = SystemClock.elapsedRealtime()
@@ -501,32 +641,27 @@
                 val afterGetOp = SystemClock.elapsedRealtime()
 
                 assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
                 assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!
-                        .getLastDuration(OP_FLAGS_ALL)).isIn(
-                        beforeAttributionStop - afterAttributionStart
-                                ..afterAttributionStop - beforeAttributionStart)
+                    .getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeAttrStop - afterAttrStart..afterAttrStop - beforeAttrStart)
                 assertThat(getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeAttributionStop - afterAttributionStart
-                                ..afterAttributionStop - beforeAttributionStart)
+                    .isIn(beforeAttrStop - afterAttrStart..afterAttrStop - beforeAttrStart)
             }
         }
 
-        val beforeNullAttributionStop = SystemClock.elapsedRealtime()
+        val beforeNullAttrStop = SystemClock.elapsedRealtime()
         appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, null)
-        val afterNullAttributionStop = SystemClock.elapsedRealtime()
+        val afterNullAttrStop = SystemClock.elapsedRealtime()
 
         with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
             assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                    .isIn(beforeNullAttributionStop - afterNullAttributionStart
-                            ..afterNullAttributionStop - beforeNullAttributionStart)
+                .isIn(beforeNullAttrStop -
+                    afterNullAttrStart..afterNullAttrStop - beforeNullAttrStart)
             assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!.getLastDuration(OP_FLAGS_ALL))
-                    .isIn(beforeAttributionStop - afterAttributionStart
-                            ..afterAttributionStop - beforeAttributionStart)
+                .isIn(beforeAttrStop - afterAttrStart..afterAttrStop - beforeAttrStart)
             assertThat(getLastDuration(OP_FLAGS_ALL))
-                    .isIn(beforeAttributionStop - afterAttributionStart
-                            ..afterAttributionStop - beforeAttributionStart)
+                .isIn(beforeAttrStop - afterAttrStart..afterAttrStop - beforeAttrStart)
         }
     }
 }
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
index ea20c6d..6e8d612 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
@@ -16,14 +16,20 @@
 
 package android.app.appops.cts
 
+import android.Manifest.permission.READ_CONTACTS
+import android.Manifest.permission.READ_LOGS
+import android.app.Activity.RESULT_OK
 import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
 import android.app.AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY
 import android.app.AppOpsManager.OPSTR_CAMERA
 import android.app.AppOpsManager.OPSTR_COARSE_LOCATION
 import android.app.AppOpsManager.OPSTR_FINE_LOCATION
 import android.app.AppOpsManager.OPSTR_GET_ACCOUNTS
+import android.app.AppOpsManager.OPSTR_GET_USAGE_STATS
 import android.app.AppOpsManager.OPSTR_READ_CONTACTS
 import android.app.AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE
+import android.app.AppOpsManager.OPSTR_SEND_SMS
 import android.app.AppOpsManager.OPSTR_WRITE_CONTACTS
 import android.app.AppOpsManager.OnOpNotedCallback
 import android.app.AppOpsManager.strOpToOp
@@ -33,8 +39,6 @@
 import android.app.WallpaperManager
 import android.app.WallpaperManager.FLAG_SYSTEM
 import android.bluetooth.BluetoothManager
-import android.bluetooth.cts.BTAdapterUtils.enableAdapter as enableBTAdapter
-import android.bluetooth.cts.BTAdapterUtils.disableAdapter as disableBTAdapter
 import android.bluetooth.le.ScanCallback
 import android.content.BroadcastReceiver
 import android.content.ComponentName
@@ -55,11 +59,14 @@
 import android.location.LocationManager
 import android.net.wifi.WifiManager
 import android.os.Bundle
+import android.os.DropBoxManager
 import android.os.Handler
 import android.os.IBinder
 import android.os.Looper
+import android.os.Process.myUserHandle
 import android.platform.test.annotations.AppModeFull
 import android.provider.ContactsContract
+import android.telephony.SmsManager
 import android.telephony.TelephonyManager
 import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
@@ -67,14 +74,20 @@
 import org.junit.Assert.fail
 import org.junit.Assume.assumeTrue
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit.MILLISECONDS
 import java.util.concurrent.TimeoutException
+import android.bluetooth.cts.BTAdapterUtils.disableAdapter as disableBTAdapter
+import android.bluetooth.cts.BTAdapterUtils.enableAdapter as enableBTAdapter
 
 private const val TEST_SERVICE_PKG = "android.app.appops.cts.appthatusesappops"
 private const val TIMEOUT_MILLIS = 10000L
+private const val PRIVATE_ACTION = "android.app.appops.cts.PRIVATE_ACTION"
+private const val PUBLIC_ACTION = "android.app.appops.cts.PUBLIC_ACTION"
+private const val PROTECTED_ACTION = "android.app.appops.cts.PROTECTED_ACTION"
 
 private external fun nativeNoteOp(
     op: Int,
@@ -86,7 +99,7 @@
 
 @AppModeFull(reason = "Test relies on other app to connect to. Instant apps can't see other apps")
 class AppOpsLoggingTest {
-    private val context = InstrumentationRegistry.getInstrumentation().targetContext
+    private val context = InstrumentationRegistry.getInstrumentation().targetContext as Context
     private val appOpsManager = context.getSystemService(AppOpsManager::class.java)
 
     private val myUid = android.os.Process.myUid()
@@ -520,7 +533,9 @@
 
     /**
      * Realistic end-to-end test for getting called back for a proximity alert
+     * (b/150438846 - ignored this test due to flakiness)
      */
+    @Ignore
     @Test
     fun triggerProximityAlert() {
         val PROXIMITY_ALERT_ACTION = "proxAlert"
@@ -555,7 +570,12 @@
                 eventually {
                     assertThat(asyncNoted.map { it.op }).contains(OPSTR_FINE_LOCATION)
                     assertThat(asyncNoted[0].attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
-                    assertThat(asyncNoted[0].message).contains(PROXIMITY_ALERT_ACTION)
+
+                    assertThat(asyncNoted[0].message).contains(
+                        proximityAlertReceiverPendingIntent::class.java.name)
+                    assertThat(asyncNoted[0].message).contains(
+                        Integer.toHexString(
+                            System.identityHashCode(proximityAlertReceiverPendingIntent)))
                 }
             } finally {
                 locationManager.removeProximityAlert(proximityAlertReceiverPendingIntent)
@@ -669,6 +689,24 @@
     }
 
     /**
+     * Realistic end-to-end test for sending a SMS message
+     */
+    @Test
+    fun sendSms() {
+        assumeTrue(context.packageManager.hasSystemFeature(FEATURE_TELEPHONY))
+
+        val smsManager = context.createAttributionContext(TEST_ATTRIBUTION_TAG)
+                .getSystemService(SmsManager::class.java)
+
+        // No need for valid data. The permission is checked before the parameters are validated
+        smsManager.sendTextMessage("dst", null, "text", null, null)
+
+        assertThat(noted[0].first.op).isEqualTo(OPSTR_SEND_SMS)
+        assertThat(noted[0].first.attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
+        assertThat(noted[0].second.map { it.methodName }).contains("sendSms")
+    }
+
+    /**
      * Realistic end-to-end test for starting a permission protected activity
      */
     @Test
@@ -683,6 +721,77 @@
         assertThat(noted[0].second.map { it.methodName }).contains("startActivity")
     }
 
+    /**
+     * Realistic end-to-end test for starting a permission protected activity
+     */
+    @Test
+    fun getNextDropBoxEntry() {
+        runWithShellPermissionIdentity {
+            context.packageManager.grantRuntimePermission(myPackage, READ_LOGS, myUserHandle())
+            appOpsManager.setMode(OPSTR_GET_USAGE_STATS, myUid, myPackage, MODE_ALLOWED)
+        }
+
+        val dropBoxManager = context.createAttributionContext(TEST_ATTRIBUTION_TAG)
+                .getSystemService(DropBoxManager::class.java)
+
+        val entry = dropBoxManager.getNextEntry("foo", 100)
+        entry?.close()
+
+        assertThat(noted[0].first.op).isEqualTo(OPSTR_GET_USAGE_STATS)
+        assertThat(noted[0].first.attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
+        assertThat(noted[0].second.map { it.methodName }).contains("getNextDropBoxEntry")
+    }
+
+    @Test
+    fun receiveBroadcastRegisteredReceiver() {
+        val receiver = object : BroadcastReceiver() {
+            override fun onReceive(context: Context?, intent: Intent?) {
+            }
+        }
+
+        val testContext = context.createAttributionContext(TEST_ATTRIBUTION_TAG)
+        testContext.registerReceiver(receiver, IntentFilter(PRIVATE_ACTION))
+
+        try {
+            context.sendOrderedBroadcast(Intent(PRIVATE_ACTION), READ_CONTACTS, OPSTR_READ_CONTACTS,
+                    null, null, RESULT_OK, null, null)
+
+            eventually {
+                assertThat(asyncNoted[0].op).isEqualTo(OPSTR_READ_CONTACTS)
+                assertThat(asyncNoted[0].attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
+                assertThat(asyncNoted[0].message)
+                        .contains(System.identityHashCode(receiver).toString())
+            }
+        } finally {
+            testContext.unregisterReceiver(receiver)
+        }
+    }
+
+    @Test
+    fun receiveBroadcastManifestReceiver() {
+        context.sendOrderedBroadcast(Intent(PUBLIC_ACTION).setPackage(myPackage), READ_CONTACTS,
+                OPSTR_READ_CONTACTS, null, null, RESULT_OK, null, null)
+
+        eventually {
+            assertThat(asyncNoted[0].op).isEqualTo(OPSTR_READ_CONTACTS)
+
+            // Manifest receivers do not have an attribution
+            assertThat(asyncNoted[0].attributionTag).isEqualTo(null)
+            assertThat(asyncNoted[0].message).contains("PublicActionReceiver")
+        }
+    }
+
+    @Test
+    fun sendBroadcastToProtectedReceiver() {
+        context.createAttributionContext(TEST_ATTRIBUTION_TAG)
+                .sendBroadcast(Intent(PROTECTED_ACTION).setPackage(myPackage))
+
+        eventually {
+            assertThat(asyncNoted[0].op).isEqualTo(OPSTR_READ_CONTACTS)
+            assertThat(asyncNoted[0].attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
+        }
+    }
+
     @After
     fun removeNotedAppOpsCollector() {
         appOpsManager.setOnOpNotedCallback(null, null)
@@ -841,3 +950,13 @@
         }
     }
 }
+
+class PublicActionReceiver : BroadcastReceiver() {
+    override fun onReceive(context: Context, intent: Intent?) {
+    }
+}
+
+class ProtectedActionReceiver : BroadcastReceiver() {
+    override fun onReceive(context: Context, intent: Intent?) {
+    }
+}
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
index 3b4dda1..dae5d3a 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
@@ -249,17 +249,18 @@
             mAppOps.startWatchingActive(arrayOf(OPSTR_WRITE_CALENDAR), Executor { it.run() },
                 activeWatcher)
             try {
-                mAppOps.startOp(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName, "attribution1", null)
+                mAppOps.startOp(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName, "firstAttribution",
+                        null)
                 assertTrue(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
                 gotActive.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
 
                 mAppOps.startOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
-                    "attribution2", null)
+                    "secondAttribution", null)
                 assertTrue(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
                 assertFalse(gotInActive.isDone)
 
                 mAppOps.finishOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
-                    "attribution1")
+                    "firstAttribution")
 
                 // Allow some time for premature "watchingActive" callbacks to arrive
                 Thread.sleep(500)
@@ -268,7 +269,7 @@
                 assertFalse(gotInActive.isDone)
 
                 mAppOps.finishOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
-                    "attribution2")
+                    "secondAttribution")
                 assertFalse(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
                 gotInActive.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
             } finally {
diff --git a/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt b/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
index 0bdf312..33ccf9a 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
@@ -19,12 +19,12 @@
 import android.app.AppOpsManager
 import android.app.AppOpsManager.OPSTR_WIFI_SCAN
 import android.app.AppOpsManager.OP_FLAGS_ALL
+import android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE
 import android.platform.test.annotations.AppModeFull
 import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
-import java.lang.AssertionError
 import java.lang.Thread.sleep
 
 private const val APK_PATH = "/data/local/tmp/cts/appops/"
@@ -62,7 +62,7 @@
         sleep(1)
 
         runWithShellPermissionIdentity {
-            appOpsManager.noteOpNoThrow(OPSTR_WIFI_SCAN, appUid, APP_PKG, attribution, null)
+            appOpsManager.noteOp(OPSTR_WIFI_SCAN, appUid, APP_PKG, attribution, null)
         }
     }
 
@@ -106,6 +106,18 @@
         }
     }
 
+    @Test(expected = SecurityException::class)
+    fun cannotUseUndeclaredAttributionTag() {
+        noteForAttribution("invalid attribution tag")
+    }
+
+    @Test
+    fun canUseUndeclaredAttributionTagIfChangeForBlameeIsDisabled() {
+        withDisabledCompatChange(SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, APP_PKG) {
+            noteForAttribution("invalid attribution tag")
+        }
+    }
+
     @Test(expected = AssertionError::class)
     fun cannotInheritFromSelf() {
         installApk("AppWithAttributionInheritingFromSelf.apk")
diff --git a/tests/tests/appop2/Android.bp b/tests/tests/appop2/Android.bp
new file mode 100644
index 0000000..4b6eed8
--- /dev/null
+++ b/tests/tests/appop2/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsAppOps2TestCases",
+
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "appops-test-util-lib",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "platform-test-annotations",
+        "truth-prebuilt",
+    ],
+
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/appop2/AndroidManifest.xml b/tests/tests/appop2/AndroidManifest.xml
new file mode 100644
index 0000000..b1ceac6
--- /dev/null
+++ b/tests/tests/appop2/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.app.appops2.cts">
+  <attribution android:tag="testAttribution" android:label="@string/dummyLabel" />
+
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+  <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+
+  <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+      android:functionalTest="true"
+      android:targetPackage="android.app.appops2.cts"
+      android:label="Tests for the app ops API (cannot include reset of app-op state)."/>
+
+</manifest>
diff --git a/tests/tests/appop2/AndroidTest.xml b/tests/tests/appop2/AndroidTest.xml
new file mode 100644
index 0000000..fc10a83
--- /dev/null
+++ b/tests/tests/appop2/AndroidTest.xml
@@ -0,0 +1,45 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Config for CTS app ops test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsAppOps2TestCases.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="appops set android.app.appops2.cts REQUEST_INSTALL_PACKAGES allow" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/appops2" />
+        <option name="teardown-command" value="pm uninstall android.app.appops.cts.apptoblame" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts" />
+    </target_preparer>
+
+    <!-- Load additional APKs onto device -->
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer" >
+        <option name="push-file" key="CtsAppToBlame1.apk" value="/data/local/tmp/cts/appops2/CtsAppToBlame1.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="hidden-api-checks" value="true" />
+        <option name="package" value="android.app.appops2.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/appop2/OWNERS b/tests/tests/appop2/OWNERS
new file mode 100644
index 0000000..3753dee
--- /dev/null
+++ b/tests/tests/appop2/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 137825
+moltmann@google.com
+zhanghai@google.com
+ntmyren@google.com
+eugenesusla@google.com
+svetoslavganov@google.com
+evanseverson@google.com
diff --git a/tests/tests/appop2/TEST_MAPPING b/tests/tests/appop2/TEST_MAPPING
new file mode 100644
index 0000000..dbf2444
--- /dev/null
+++ b/tests/tests/appop2/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppOps2TestCases"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/tests/appop2/res/values/strings.xml b/tests/tests/appop2/res/values/strings.xml
new file mode 100644
index 0000000..ab27f6a
--- /dev/null
+++ b/tests/tests/appop2/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dummyLabel">A feature</string>
+</resources>
diff --git a/tests/tests/appop2/src/android/app/appops2/cts/AppOpsLoggingTest.kt b/tests/tests/appop2/src/android/app/appops2/cts/AppOpsLoggingTest.kt
new file mode 100644
index 0000000..a2d5faa
--- /dev/null
+++ b/tests/tests/appop2/src/android/app/appops2/cts/AppOpsLoggingTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appops2.cts
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.OnOpNotedCallback
+import android.app.AppOpsManager.permissionToOp
+import android.app.AsyncNotedAppOp
+import android.app.PendingIntent
+import android.app.SyncNotedAppOp
+import android.app.appops.cts.eventually
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.IntentFilter
+import android.content.pm.PackageInstaller.EXTRA_STATUS
+import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID
+import android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION
+import android.content.pm.PackageInstaller.SessionParams
+import android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
+import android.platform.test.annotations.AppModeFull
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+import java.util.concurrent.Executor
+
+private const val TEST_ATTRIBUTION_TAG = "testAttribution";
+
+@AppModeFull(reason = "Test relies on other app to connect to. Instant apps can't see other apps")
+class AppOpsLoggingTest {
+    private val context = InstrumentationRegistry.getInstrumentation().targetContext
+    private val appOpsManager = context.getSystemService(AppOpsManager::class.java)
+
+    // Collected note-op calls inside of this process
+    private val noted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+    private val selfNoted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+    private val asyncNoted = mutableListOf<AsyncNotedAppOp>()
+
+    @Before
+    fun setNotedAppOpsCollectorAndClearCollectedNoteOps() {
+        setNotedAppOpsCollector()
+        clearCollectedNotedOps()
+    }
+
+    private fun clearCollectedNotedOps() {
+        noted.clear()
+        selfNoted.clear()
+        asyncNoted.clear()
+    }
+
+    private fun setNotedAppOpsCollector() {
+        appOpsManager.setOnOpNotedCallback(Executor { it.run() },
+                object : OnOpNotedCallback() {
+                    override fun onNoted(op: SyncNotedAppOp) {
+                        noted.add(op to Throwable().stackTrace)
+                    }
+
+                    override fun onSelfNoted(op: SyncNotedAppOp) {
+                        selfNoted.add(op to Throwable().stackTrace)
+                    }
+
+                    override fun onAsyncNoted(asyncOp: AsyncNotedAppOp) {
+                        asyncNoted.add(asyncOp)
+                    }
+                })
+    }
+
+    /**
+     * Realistic end-to-end test for requesting to install a package
+     */
+    @Test
+    fun requestInstall() {
+        val pi = context.createAttributionContext(TEST_ATTRIBUTION_TAG).packageManager
+                .packageInstaller
+        val sessionId = pi.createSession(SessionParams(MODE_FULL_INSTALL))
+
+        val session = pi.openSession(sessionId)
+        try {
+            // Write apk data to session
+            File("/data/local/tmp/cts/appops2/CtsAppToBlame1.apk")
+                    .inputStream().use { fileOnDisk ->
+                        session.openWrite("base.apk", 0, -1).use { sessionFile ->
+                            fileOnDisk.copyTo(sessionFile)
+                        }
+                    }
+
+            val installAction = context.packageName + ".install_cb"
+            context.registerReceiver(object : BroadcastReceiver() {
+                override fun onReceive(ignored: Context?, intent: Intent) {
+                    if (intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID)
+                            != STATUS_PENDING_USER_ACTION) {
+                        return
+                    }
+
+                    // Start package install request UI (should trigger REQUEST_INSTALL_PACKAGES)
+                    val activityIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
+                    activityIntent!!.addFlags(
+                            FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK)
+                    context.startActivity(activityIntent)
+                }
+            }, IntentFilter(installAction))
+
+            // Commit session (should trigger installAction receiver)
+            session.commit(PendingIntent.getBroadcast(context, 0, Intent(installAction),
+                    PendingIntent.FLAG_UPDATE_CURRENT).intentSender)
+
+            eventually {
+                assertThat(asyncNoted[0].op).isEqualTo(
+                        permissionToOp(android.Manifest.permission.REQUEST_INSTALL_PACKAGES))
+                assertThat(asyncNoted[0].attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
+            }
+        } finally {
+            session.abandon()
+        }
+    }
+
+    @After
+    fun removeNotedAppOpsCollector() {
+        appOpsManager.setOnOpNotedCallback(null, null)
+    }
+}
diff --git a/tests/tests/appwidget/AndroidManifest.xml b/tests/tests/appwidget/AndroidManifest.xml
index 4c23851..645bdc5 100644
--- a/tests/tests/appwidget/AndroidManifest.xml
+++ b/tests/tests/appwidget/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -17,81 +16,88 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.appwidget.cts"
-    android:targetSandboxVersion="2">
+     package="android.appwidget.cts"
+     android:targetSandboxVersion="2">
 
   <application>
       <uses-library android:name="android.test.runner"/>
 
-      <receiver android:name="android.appwidget.cts.provider.FirstAppWidgetProvider" >
+      <receiver android:name="android.appwidget.cts.provider.FirstAppWidgetProvider"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/first_appwidget_info" />
+               android:resource="@xml/first_appwidget_info"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.SecondAppWidgetProvider" >
+      <receiver android:name="android.appwidget.cts.provider.SecondAppWidgetProvider"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/second_appwidget_info" />
+               android:resource="@xml/second_appwidget_info"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider1" >
+      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider1"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/appwidget_info_with_feature1" />
+               android:resource="@xml/appwidget_info_with_feature1"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider2" >
+      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider2"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/appwidget_info_with_feature2" />
+               android:resource="@xml/appwidget_info_with_feature2"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider3" >
+      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider3"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/appwidget_info_with_feature3" />
+               android:resource="@xml/appwidget_info_with_feature3"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.CollectionAppWidgetProvider" >
+      <receiver android:name="android.appwidget.cts.provider.CollectionAppWidgetProvider"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/collection_appwidget_info" />
+               android:resource="@xml/collection_appwidget_info"/>
       </receiver>
 
       <service android:name="android.appwidget.cts.service.MyAppWidgetService"
-          android:permission="android.permission.BIND_REMOTEVIEWS">
+           android:permission="android.permission.BIND_REMOTEVIEWS">
       </service>
 
       <activity android:name="android.appwidget.cts.activity.EmptyActivity"
-                android:label="EmptyActivity">
+           android:label="EmptyActivity"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.MAIN" />
-              <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+              <action android:name="android.intent.action.MAIN"/>
+              <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
           </intent-filter>
       </activity>
 
       <activity android:name="android.appwidget.cts.activity.TransitionActivity"
-          android:label="TransitionActivity" />
+           android:label="TransitionActivity"/>
   </application>
 
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-      android:targetPackage="android.appwidget.cts"
-      android:label="Tests for the app widget APIs.">
+       android:targetPackage="android.appwidget.cts"
+       android:label="Tests for the app widget APIs.">
       <meta-data android:name="listener"
-          android:value="com.android.cts.runner.CtsTestRunListener" />
+           android:value="com.android.cts.runner.CtsTestRunListener"/>
   </instrumentation>
 
 </manifest>
diff --git a/tests/tests/appwidget/packages/launchermanifest/AndroidManifest-pinActivity.xml b/tests/tests/appwidget/packages/launchermanifest/AndroidManifest-pinActivity.xml
index 47d1e05..1decc84 100644
--- a/tests/tests/appwidget/packages/launchermanifest/AndroidManifest-pinActivity.xml
+++ b/tests/tests/appwidget/packages/launchermanifest/AndroidManifest-pinActivity.xml
@@ -16,12 +16,13 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.appwidget.cts.packages">
+     package="android.appwidget.cts.packages">
 
     <application>
-        <activity android:name="AppWidgetConfirmPin">
+        <activity android:name="AppWidgetConfirmPin"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET" />
+                <action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/appwidget/packages/launchermanifest/AndroidManifest.xml b/tests/tests/appwidget/packages/launchermanifest/AndroidManifest.xml
index 57c4e4d..0c6707f 100644
--- a/tests/tests/appwidget/packages/launchermanifest/AndroidManifest.xml
+++ b/tests/tests/appwidget/packages/launchermanifest/AndroidManifest.xml
@@ -16,15 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.appwidget.cts.packages"
-          android:targetSandboxVersion="2">
+     package="android.appwidget.cts.packages"
+     android:targetSandboxVersion="2">
 
     <application>
-        <activity android:name="Launcher">
+        <activity android:name="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml
index 2c1db2f..1d4e17f 100644
--- a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml
@@ -16,18 +16,19 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.appwidget.cts.widgetprovider">
+     package="android.appwidget.cts.widgetprovider">
 
     <application>
-        <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+        <receiver android:name="android.appwidget.cts.packages.SimpleProvider"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE"/>
             </intent-filter>
             <meta-data android:name="android.appwidget.provider"
-                       android:resource="@xml/widget_no_config" />
+                 android:resource="@xml/widget_no_config"/>
             <meta-data android:name="my_custom_info"
-                       android:resource="@xml/widget_config" />
+                 android:resource="@xml/widget_config"/>
         </receiver>
     </application>
 </manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml
index 8a070b4..6674f54 100644
--- a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml
@@ -16,18 +16,19 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.appwidget.cts.widgetprovider">
+     package="android.appwidget.cts.widgetprovider">
 
     <application>
-        <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+        <receiver android:name="android.appwidget.cts.packages.SimpleProvider"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE"/>
             </intent-filter>
             <meta-data android:name="android.appwidget.provider"
-                       android:resource="@xml/widget_no_config" />
+                 android:resource="@xml/widget_no_config"/>
             <meta-data android:name="my_custom_info"
-                       android:resource="@xml/widget_config_no_resize" />
+                 android:resource="@xml/widget_config_no_resize"/>
         </receiver>
     </application>
 </manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml
index f6f012e..ac77f90 100644
--- a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml
@@ -16,16 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.appwidget.cts.widgetprovider">
+     package="android.appwidget.cts.widgetprovider">
 
     <application>
-        <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+        <receiver android:name="android.appwidget.cts.packages.SimpleProvider"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE"/>
             </intent-filter>
             <meta-data android:name="android.appwidget.provider"
-                       android:resource="@xml/widget_no_config" />
+                 android:resource="@xml/widget_no_config"/>
         </receiver>
     </application>
 </manifest>
diff --git a/tests/tests/assist/AndroidManifest.xml b/tests/tests/assist/AndroidManifest.xml
index feff7c4..2d68e1a 100644
--- a/tests/tests/assist/AndroidManifest.xml
+++ b/tests/tests/assist/AndroidManifest.xml
@@ -16,40 +16,40 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.assist.cts">
+     package="android.assist.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <application>
-      <uses-library android:name="android.test.runner" />
+      <uses-library android:name="android.test.runner"/>
       <!-- resizeableActivity makes the TestStartActivity run on Primary display to accommodate
-           stack behavior assumptions in this test. See b/70032125 -->
+                     stack behavior assumptions in this test. See b/70032125 -->
       <activity android:name="android.assist.cts.TestStartActivity"
-                android:label="Assist Test Start Activity"
-                android:resizeableActivity="false">
+           android:label="Assist Test Start Activity"
+           android:resizeableActivity="false"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_ASSIST_STRUCTURE" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_DISABLE_CONTEXT" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_FLAG_SECURE" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_LIFECYCLE" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_SCREENSHOT" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_EXTRA_ASSIST" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_TEXTVIEW" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_LARGE_VIEW_HIERARCHY" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_WEBVIEW" />
-              <category android:name="android.intent.category.LAUNCHER" />
-              <category android:name="android.intent.category.DEFAULT" />
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_ASSIST_STRUCTURE"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_DISABLE_CONTEXT"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_FLAG_SECURE"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_LIFECYCLE"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_SCREENSHOT"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_EXTRA_ASSIST"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_TEXTVIEW"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_LARGE_VIEW_HIERARCHY"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_WEBVIEW"/>
+              <category android:name="android.intent.category.LAUNCHER"/>
+              <category android:name="android.intent.category.DEFAULT"/>
           </intent-filter>
       </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.assist.cts"
-                     android:label="CTS tests of android.assist">
+         android:targetPackage="android.assist.cts"
+         android:label="CTS tests of android.assist">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/assist/service/AndroidManifest.xml b/tests/tests/assist/service/AndroidManifest.xml
index 05da5fd..e116a1c 100644
--- a/tests/tests/assist/service/AndroidManifest.xml
+++ b/tests/tests/assist/service/AndroidManifest.xml
@@ -16,62 +16,64 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.assist.service">
+     package="android.assist.service">
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <application>
-      <uses-library android:name="android.test.runner" />
+      <uses-library android:name="android.test.runner"/>
       <service android:name=".MainInteractionService"
-               android:label="CTS test voice interaction service"
-               android:permission="android.permission.BIND_VOICE_INTERACTION"
-               android:process=":interactor"
-               android:exported="true"
-               android:visibleToInstantApps="true">
+           android:label="CTS test voice interaction service"
+           android:permission="android.permission.BIND_VOICE_INTERACTION"
+           android:process=":interactor"
+           android:exported="true"
+           android:visibleToInstantApps="true">
           <meta-data android:name="android.voice_interaction"
-                     android:resource="@xml/interaction_service" />
+               android:resource="@xml/interaction_service"/>
           <intent-filter>
-              <action android:name="android.service.voice.VoiceInteractionService" />
+              <action android:name="android.service.voice.VoiceInteractionService"/>
           </intent-filter>
       </service>
       <activity android:name=".DisableContextActivity"
-                android:visibleToInstantApps="true">
+           android:visibleToInstantApps="true"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.START_TEST_DISABLE_CONTEXT" />
-              <category android:name="android.intent.category.DEFAULT" />
+              <action android:name="android.intent.action.START_TEST_DISABLE_CONTEXT"/>
+              <category android:name="android.intent.category.DEFAULT"/>
           </intent-filter>
       </activity>
       <activity android:name=".DelayedAssistantActivity"
-                android:label="Delay Assistant Start Activity"
-                android:exported="true"
-                android:visibleToInstantApps="true">
+           android:label="Delay Assistant Start Activity"
+           android:exported="true"
+           android:visibleToInstantApps="true">
           <intent-filter>
-              <action android:name="android.intent.action.START_TEST_ASSIST_STRUCTURE" />
-              <action android:name="android.intent.action.START_TEST_LIFECYCLE" />
-              <action android:name="android.intent.action.START_TEST_LIFECYCLE_NOUI" />
-              <action android:name="android.intent.action.START_TEST_FLAG_SECURE" />
-              <action android:name="android.intent.action.START_TEST_SCREENSHOT" />
-              <action android:name="android.intent.action.START_TEST_EXTRA_ASSIST" />
-              <action android:name="android.intent.action.START_TEST_TEXTVIEW" />
-              <action android:name="android.intent.action.START_TEST_LARGE_VIEW_HIERARCHY" />
-              <action android:name="android.intent.action.START_TEST_VERIFY_CONTENT_VIEW" />
-              <action android:name="android.intent.action.START_TEST_FOCUS_CHANGE" />
-              <action android:name="android.intent.action.START_TEST_WEBVIEW" />
-              <category android:name="android.intent.category.DEFAULT" />
+              <action android:name="android.intent.action.START_TEST_ASSIST_STRUCTURE"/>
+              <action android:name="android.intent.action.START_TEST_LIFECYCLE"/>
+              <action android:name="android.intent.action.START_TEST_LIFECYCLE_NOUI"/>
+              <action android:name="android.intent.action.START_TEST_FLAG_SECURE"/>
+              <action android:name="android.intent.action.START_TEST_SCREENSHOT"/>
+              <action android:name="android.intent.action.START_TEST_EXTRA_ASSIST"/>
+              <action android:name="android.intent.action.START_TEST_TEXTVIEW"/>
+              <action android:name="android.intent.action.START_TEST_LARGE_VIEW_HIERARCHY"/>
+              <action android:name="android.intent.action.START_TEST_VERIFY_CONTENT_VIEW"/>
+              <action android:name="android.intent.action.START_TEST_FOCUS_CHANGE"/>
+              <action android:name="android.intent.action.START_TEST_WEBVIEW"/>
+              <category android:name="android.intent.category.DEFAULT"/>
           </intent-filter>
       </activity>
       <service android:name=".MainInteractionSessionService"
-              android:permission="android.permission.BIND_VOICE_INTERACTION"
-              android:process=":session">
+           android:permission="android.permission.BIND_VOICE_INTERACTION"
+           android:process=":session">
       </service>
       <service android:name=".MainRecognitionService"
-              android:label="CTS Voice Recognition Service">
+           android:label="CTS Voice Recognition Service"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.speech.RecognitionService" />
-              <category android:name="android.intent.category.DEFAULT" />
+              <action android:name="android.speech.RecognitionService"/>
+              <category android:name="android.intent.category.DEFAULT"/>
           </intent-filter>
-          <meta-data android:name="android.speech" android:resource="@xml/recognition_service" />
+          <meta-data android:name="android.speech"
+               android:resource="@xml/recognition_service"/>
       </service>
     </application>
 </manifest>
-
diff --git a/tests/tests/assist/testapp/AndroidManifest.xml b/tests/tests/assist/testapp/AndroidManifest.xml
index 513d27a..a2dcd7a 100644
--- a/tests/tests/assist/testapp/AndroidManifest.xml
+++ b/tests/tests/assist/testapp/AndroidManifest.xml
@@ -16,79 +16,87 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.assist.testapp">
+     package="android.assist.testapp">
 
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name=".TestApp"
-                android:label="Assist Structure Test Activity">
+             android:label="Assist Structure Test Activity"
+             android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.TEST_APP_ASSIST_STRUCTURE" />
-              <action android:name="android.intent.action.TEST_APP_LARGE_VIEW_HIERARCHY" />
-              <category android:name="android.intent.category.DEFAULT" />
-              <category android:name="android.intent.category.VOICE" />
+              <action android:name="android.intent.action.TEST_APP_ASSIST_STRUCTURE"/>
+              <action android:name="android.intent.action.TEST_APP_LARGE_VIEW_HIERARCHY"/>
+              <category android:name="android.intent.category.DEFAULT"/>
+              <category android:name="android.intent.category.VOICE"/>
           </intent-filter>
         </activity>
         <activity android:name=".SecureActivity"
-                  android:label="Secure Test Activity">
+             android:label="Secure Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_FLAG_SECURE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
+                <action android:name="android.intent.action.TEST_APP_FLAG_SECURE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.VOICE"/>
             </intent-filter>
         </activity>
         <activity android:name=".LifecycleActivity"
-                  android:label="Life Cycle Check Activity">
+             android:label="Life Cycle Check Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_LIFECYCLE" />
-                <action android:name="android.intent.action.TEST_APP_LIFECYCLE_NOUI" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
+                <action android:name="android.intent.action.TEST_APP_LIFECYCLE"/>
+                <action android:name="android.intent.action.TEST_APP_LIFECYCLE_NOUI"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.VOICE"/>
             </intent-filter>
         </activity>
         <!-- resizeableActivity makes the ScreenshotActivity run on Primary display to accommodate
-             assumptions about screenshot display vs TestStartActivity display in this test.
-             See b/70032125 -->
+                         assumptions about screenshot display vs TestStartActivity display in this test.
+                         See b/70032125 -->
         <activity android:name=".ScreenshotActivity"
-                  android:label="Screenshot Test Activity"
-                  android:resizeableActivity="false">
+             android:label="Screenshot Test Activity"
+             android:resizeableActivity="false"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_SCREENSHOT" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
+                <action android:name="android.intent.action.TEST_APP_SCREENSHOT"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.VOICE"/>
             </intent-filter>
         </activity>
         <activity android:name=".ExtraAssistDataActivity"
-            android:label="Extra Assist Test Activity">
+             android:label="Extra Assist Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_EXTRA_ASSIST" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
+                <action android:name="android.intent.action.TEST_APP_EXTRA_ASSIST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.VOICE"/>
             </intent-filter>
         </activity>
         <activity android:name=".TextViewActivity"
-            android:label="TextView Test Activity">
+             android:label="TextView Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_TEXTVIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.TEST_APP_TEXTVIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <activity android:name=".WebViewActivity"
-            android:label="WebView Test Activity">
+             android:label="WebView Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_WEBVIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.TEST_APP_WEBVIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <activity android:name=".FocusChangeActivity"
-            android:label="Focus Change Test Activity">
+             android:label="Focus Change Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_FOCUS_CHANGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
+                <action android:name="android.intent.action.TEST_APP_FOCUS_CHANGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.VOICE"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/batterysaving/Android.bp b/tests/tests/batterysaving/Android.bp
index bbcd8d7..28cd890 100644
--- a/tests/tests/batterysaving/Android.bp
+++ b/tests/tests/batterysaving/Android.bp
@@ -34,6 +34,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "test_current",
 }
diff --git a/tests/tests/batterysaving/AndroidTest.xml b/tests/tests/batterysaving/AndroidTest.xml
index 5f32612..ad98752 100644
--- a/tests/tests/batterysaving/AndroidTest.xml
+++ b/tests/tests/batterysaving/AndroidTest.xml
@@ -47,4 +47,9 @@
         <option name="package" value="android.os.cts.batterysaving" />
         <option name="runtime-hint" value="10m00s" />
     </test>
+
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="android.scheduling"/>
+    </object>
 </configuration>
diff --git a/tests/tests/batterysaving/apps/app_target_api_25/Android.bp b/tests/tests/batterysaving/apps/app_target_api_25/Android.bp
index b65c72d..754b28c 100644
--- a/tests/tests/batterysaving/apps/app_target_api_25/Android.bp
+++ b/tests/tests/batterysaving/apps/app_target_api_25/Android.bp
@@ -22,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/batterysaving/apps/app_target_api_current/Android.bp b/tests/tests/batterysaving/apps/app_target_api_current/Android.bp
index 0fe80fd..f5d5543 100644
--- a/tests/tests/batterysaving/apps/app_target_api_current/Android.bp
+++ b/tests/tests/batterysaving/apps/app_target_api_current/Android.bp
@@ -21,6 +21,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
 
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java
index e4abb12..3bc90f1 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java
@@ -22,7 +22,6 @@
 import static com.android.compatibility.common.util.AmUtils.runMakeUidIdle;
 import static com.android.compatibility.common.util.BatteryUtils.enableBatterySaver;
 import static com.android.compatibility.common.util.BatteryUtils.runDumpsysBatteryUnplug;
-import static com.android.compatibility.common.util.SettingsUtils.putGlobalSetting;
 import static com.android.compatibility.common.util.TestUtils.waitUntil;
 
 import static org.junit.Assert.assertEquals;
@@ -41,12 +40,15 @@
 import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest.SetAlarmRequest;
 import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest.StartServiceRequest;
 import android.os.cts.batterysaving.common.Values;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.util.Log;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.ThreadUtils;
 
 import org.junit.After;
@@ -76,16 +78,27 @@
     private static final long ALLOW_WHILE_IDLE_LONG_TIME = 20_000;
     private static final long MIN_FUTURITY = 2_000;
 
-    private void updateAlarmManagerConstants() throws IOException {
-        putGlobalSetting("alarm_manager_constants",
-                "min_interval=" + MIN_REPEATING_INTERVAL + ","
-                + "min_futurity=" + MIN_FUTURITY + ","
-                + "allow_while_idle_short_time=" + ALLOW_WHILE_IDLE_SHORT_TIME + ","
-                + "allow_while_idle_long_time=" + ALLOW_WHILE_IDLE_LONG_TIME);
+    private void updateAlarmManagerConstants() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_ALARM_MANAGER, "min_interval",
+                    String.valueOf(MIN_REPEATING_INTERVAL), /* makeDefault */ false);
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_ALARM_MANAGER, "min_futurity",
+                    String.valueOf(MIN_FUTURITY), /* makeDefault */ false);
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_ALARM_MANAGER, "allow_while_idle_short_time",
+                    String.valueOf(ALLOW_WHILE_IDLE_SHORT_TIME), /* makeDefault */ false);
+            DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_ALARM_MANAGER, "allow_while_idle_long_time",
+                    String.valueOf(ALLOW_WHILE_IDLE_LONG_TIME), /* makeDefault */ false);
+        });
     }
 
-    private void resetAlarmManagerConstants() throws IOException {
-        putGlobalSetting("alarm_manager_constants", "null");
+    private void resetAlarmManagerConstants() {
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS,
+                        DeviceConfig.NAMESPACE_ALARM_MANAGER));
     }
 
     // Use a different broadcast action every time.
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
index e7a3272..e69de29 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
@@ -1,337 +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.os.cts.batterysaving;
-
-import static android.provider.Settings.Global.BATTERY_SAVER_CONSTANTS;
-import static android.provider.Settings.Secure.LOCATION_MODE_OFF;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.compatibility.common.util.BatteryUtils.enableBatterySaver;
-import static com.android.compatibility.common.util.BatteryUtils.runDumpsysBatteryUnplug;
-import static com.android.compatibility.common.util.BatteryUtils.turnOnScreen;
-import static com.android.compatibility.common.util.TestUtils.waitUntil;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.UiModeManager;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.location.Criteria;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.location.LocationProvider;
-import android.location.LocationRequest;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.Process;
-import android.provider.Settings;
-import android.provider.Settings.Global;
-import android.provider.Settings.Secure;
-
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.CallbackAsserter;
-import com.android.compatibility.common.util.LocationUtils;
-import com.android.compatibility.common.util.RequiredFeatureRule;
-import com.android.compatibility.common.util.SettingsUtils;
-import com.android.compatibility.common.util.TestUtils.RunnableWithThrow;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-
-/**
- * Tests related to battery saver:
- * atest android.os.cts.batterysaving.BatterySaverLocationTest
- */
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class BatterySaverLocationTest extends BatterySavingTestBase {
-    private static final String TAG = "BatterySaverLocationTest";
-
-    private static final String TEST_PROVIDER_NAME = "test_provider";
-
-    @Rule
-    public final RequiredFeatureRule mRequireLocationRule =
-            new RequiredFeatureRule(PackageManager.FEATURE_LOCATION);
-
-    @Rule
-    public final RequiredFeatureRule mRequireLocationGpsRule =
-            new RequiredFeatureRule(PackageManager.FEATURE_LOCATION_GPS);
-
-    private LocationManager mLocationManager;
-
-    private static class TestLocationListener implements LocationListener {
-        @Override
-        public void onLocationChanged(Location location) {
-
-        }
-
-        @Override
-        public void onStatusChanged(String provider, int status, Bundle extras) {
-
-        }
-
-        @Override
-        public void onProviderEnabled(String provider) {
-
-        }
-
-        @Override
-        public void onProviderDisabled(String provider) {
-            fail("Provider disabled");
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        LocationUtils.registerMockLocationProvider(getInstrumentation(), true);
-        mLocationManager = getLocationManager();
-
-        // remove test provider if left over from an aborted run
-        LocationProvider lp = mLocationManager.getProvider(TEST_PROVIDER_NAME);
-        if (lp != null) {
-            mLocationManager.removeTestProvider(TEST_PROVIDER_NAME);
-        }
-
-        SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL,
-                Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
-                "android.os.cts.batterysaving");
-
-        getContext().getSystemService(UiModeManager.class).disableCarMode(0);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        LocationUtils.registerMockLocationProvider(getInstrumentation(), false);
-        SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL,
-                Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
-                "");
-    }
-
-    private boolean areOnlyIgnoreSettingsRequestsSentToProvider() {
-        List<LocationRequest> requests =
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME);
-        for (LocationRequest request : requests) {
-            if (!request.isLocationSettingsIgnored()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Test for the {@link PowerManager#LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF} mode.
-     */
-    @Test
-    public void testLocationAllDisabled() throws Exception {
-        assertTrue("Screen is off", getPowerManager().isInteractive());
-
-        assertFalse(getPowerManager().isPowerSaveMode());
-        assertEquals(PowerManager.LOCATION_MODE_NO_CHANGE,
-                getPowerManager().getLocationPowerSaveMode());
-
-        assertEquals(0, getLocationGlobalKillSwitch());
-
-        SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL, BATTERY_SAVER_CONSTANTS, "gps_mode=2");
-        assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
-        assertTrue(getLocationManager().isLocationEnabled());
-
-        // Unplug the charger and activate battery saver.
-        runDumpsysBatteryUnplug();
-        enableBatterySaver(true);
-
-        // Skip if the location mode is not what's expected.
-        final int mode = getPowerManager().getLocationPowerSaveMode();
-        if (mode != PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF) {
-            fail("Unexpected location power save mode (" + mode + ").");
-        }
-
-        // Make sure screen is on.
-        assertTrue(getPowerManager().isInteractive());
-
-        // Make sure the kill switch is still off.
-        assertEquals(0, getLocationGlobalKillSwitch());
-
-        // Make sure location is still enabled.
-        assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
-        assertTrue(getLocationManager().isLocationEnabled());
-
-        // Turn screen off.
-        runWithExpectingLocationCallback(() -> {
-            turnOnScreen(false);
-            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 1);
-            assertEquals(LOCATION_MODE_OFF, getLocationMode());
-            assertFalse(getLocationManager().isLocationEnabled());
-        });
-
-        // On again.
-        runWithExpectingLocationCallback(() -> {
-            turnOnScreen(true);
-            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 0);
-            assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
-            assertTrue(getLocationManager().isLocationEnabled());
-        });
-
-        // Off again.
-        runWithExpectingLocationCallback(() -> {
-            turnOnScreen(false);
-            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 1);
-            assertEquals(LOCATION_MODE_OFF, getLocationMode());
-            assertFalse(getLocationManager().isLocationEnabled());
-        });
-
-        // Disable battery saver and make sure the kill swtich is off.
-        runWithExpectingLocationCallback(() -> {
-            enableBatterySaver(false);
-            waitUntil("Kill switch still on", () -> getLocationGlobalKillSwitch() == 0);
-            assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
-            assertTrue(getLocationManager().isLocationEnabled());
-        });
-    }
-
-    private int getLocationGlobalKillSwitch() {
-        return Global.getInt(getContext().getContentResolver(),
-                Global.LOCATION_GLOBAL_KILL_SWITCH, 0);
-    }
-
-    private int getLocationMode() {
-        return Secure.getInt(getContext().getContentResolver(), Secure.LOCATION_MODE, 0);
-    }
-
-    private void runWithExpectingLocationCallback(RunnableWithThrow r) throws Exception {
-        CallbackAsserter locationModeBroadcastAsserter = CallbackAsserter.forBroadcast(
-                new IntentFilter(LocationManager.MODE_CHANGED_ACTION));
-        CallbackAsserter locationModeObserverAsserter = CallbackAsserter.forContentUri(
-                Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE));
-
-        r.run();
-
-        locationModeBroadcastAsserter.assertCalled("Broadcast not received",
-                DEFAULT_TIMEOUT_SECONDS);
-        locationModeObserverAsserter.assertCalled("Observer not notified",
-                DEFAULT_TIMEOUT_SECONDS);
-    }
-
-    /**
-     * Test for the {@link PowerManager#LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF} mode.
-     */
-    @Test
-    public void testLocationRequestThrottling() throws Exception {
-        mLocationManager.addTestProvider(TEST_PROVIDER_NAME,
-                true, //requiresNetwork,
-                false, // requiresSatellite,
-                false,  // requiresCell,
-                false, // hasMonetaryCost,
-                true, // supportsAltitude,
-                false, // supportsSpeed,
-                true, // supportsBearing,
-                Criteria.POWER_MEDIUM, // powerRequirement
-                Criteria.ACCURACY_FINE); // accuracy
-        mLocationManager.setTestProviderEnabled(TEST_PROVIDER_NAME, true);
-
-        LocationRequest normalLocationRequest = LocationRequest.create()
-                .setExpireIn(300_000)
-                .setFastestInterval(0)
-                .setInterval(0)
-                .setLocationSettingsIgnored(false)
-                .setProvider(TEST_PROVIDER_NAME)
-                .setQuality(LocationRequest.ACCURACY_FINE);
-        LocationRequest ignoreSettingsLocationRequest = LocationRequest.create()
-                .setExpireIn(300_000)
-                .setFastestInterval(0)
-                .setInterval(0)
-                .setLocationSettingsIgnored(true)
-                .setProvider(TEST_PROVIDER_NAME)
-                .setQuality(LocationRequest.ACCURACY_FINE);
-        mLocationManager.requestLocationUpdates(
-                normalLocationRequest, new TestLocationListener(), Looper.getMainLooper());
-        mLocationManager.requestLocationUpdates(
-                ignoreSettingsLocationRequest, new TestLocationListener(), Looper.getMainLooper());
-        assertTrue("Not enough requests sent to provider",
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME).size() >= 2);
-        assertFalse("Normal priority requests not sent to provider",
-                areOnlyIgnoreSettingsRequestsSentToProvider());
-
-        assertTrue("Screen is off", getPowerManager().isInteractive());
-
-        SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL, BATTERY_SAVER_CONSTANTS, "gps_mode=4");
-        assertFalse(getPowerManager().isPowerSaveMode());
-        assertEquals(PowerManager.LOCATION_MODE_NO_CHANGE,
-                getPowerManager().getLocationPowerSaveMode());
-
-        // Make sure location is enabled.
-        getLocationManager().setLocationEnabledForUser(true, Process.myUserHandle());
-        assertTrue(getLocationManager().isLocationEnabled());
-
-        // Unplug the charger and activate battery saver.
-        runDumpsysBatteryUnplug();
-        enableBatterySaver(true);
-
-        // Skip if the location mode is not what's expected.
-        final int mode = getPowerManager().getLocationPowerSaveMode();
-        if (mode != PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
-            fail("Unexpected location power save mode (" + mode + "), skipping.");
-        }
-
-        // Make sure screen is on.
-        assertTrue(getPowerManager().isInteractive());
-
-        // Make sure location is still enabled.
-        assertTrue(getLocationManager().isLocationEnabled());
-
-        // Turn screen off.
-        turnOnScreen(false);
-        waitUntil("Normal location request still sent to provider",
-                this::areOnlyIgnoreSettingsRequestsSentToProvider);
-        assertTrue("Not enough requests sent to provider",
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME).size() >= 1);
-
-        // On again.
-        turnOnScreen(true);
-        waitUntil("Normal location request not sent to provider",
-                () -> !areOnlyIgnoreSettingsRequestsSentToProvider());
-        assertTrue("Not enough requests sent to provider",
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME).size() >= 2);
-
-        // Off again.
-        turnOnScreen(false);
-        waitUntil("Normal location request still sent to provider",
-                this::areOnlyIgnoreSettingsRequestsSentToProvider);
-        assertTrue("Not enough requests sent to provider",
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME).size() >= 1);
-
-
-        // Disable battery saver and make sure the kill switch is off.
-        enableBatterySaver(false);
-        waitUntil("Normal location request not sent to provider",
-                () -> !areOnlyIgnoreSettingsRequestsSentToProvider());
-        assertTrue("Not enough requests sent to provider",
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME).size() >= 2);
-    }
-}
diff --git a/tests/tests/bluetooth/TEST_MAPPING b/tests/tests/bluetooth/TEST_MAPPING
new file mode 100644
index 0000000..f349b92
--- /dev/null
+++ b/tests/tests/bluetooth/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsBluetoothTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/calendarcommon/TEST_MAPPING b/tests/tests/calendarcommon/TEST_MAPPING
new file mode 100644
index 0000000..0d71e66
--- /dev/null
+++ b/tests/tests/calendarcommon/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsCalendarcommon2TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/calendarprovider/TEST_MAPPING b/tests/tests/calendarprovider/TEST_MAPPING
new file mode 100644
index 0000000..94417b5
--- /dev/null
+++ b/tests/tests/calendarprovider/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsCalendarProviderTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/car/src/android/car/cts/CarApiTestBase.java b/tests/tests/car/src/android/car/cts/CarApiTestBase.java
index e5c343a..c328a49 100644
--- a/tests/tests/car/src/android/car/cts/CarApiTestBase.java
+++ b/tests/tests/car/src/android/car/cts/CarApiTestBase.java
@@ -24,16 +24,12 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
 import android.os.IBinder;
 import android.os.Looper;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.compatibility.common.util.RequiredFeatureRule;
-
 import org.junit.After;
-import org.junit.ClassRule;
 
 import java.util.Arrays;
 import java.util.List;
@@ -41,9 +37,6 @@
 import java.util.concurrent.TimeUnit;
 
 public abstract class CarApiTestBase {
-    @ClassRule
-    public static final RequiredFeatureRule sRequiredFeatureRule = new RequiredFeatureRule(
-            PackageManager.FEATURE_AUTOMOTIVE);
 
     protected static final long DEFAULT_WAIT_TIMEOUT_MS = 1000;
 
diff --git a/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java b/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java
index cdb9a26..4f49b34 100644
--- a/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java
@@ -117,8 +117,8 @@
 
     @Test
     public void testRegisterUnregister() throws Exception {
-        FocusChangedListerner listener = new FocusChangedListerner();
-        FocusChangedListerner listener2 = new FocusChangedListerner();
+        FocusChangedListener listener = new FocusChangedListener();
+        FocusChangedListener listener2 = new FocusChangedListener();
         mManager.addFocusListener(listener, 1);
         mManager.addFocusListener(listener2, 1);
         mManager.removeFocusListener(listener);
@@ -139,8 +139,8 @@
         final int[] emptyFocus = new int[0];
 
         Assert.assertArrayEquals(emptyFocus, mManager.getActiveAppTypes());
-        FocusChangedListerner change = new FocusChangedListerner();
-        FocusChangedListerner change2 = new FocusChangedListerner();
+        FocusChangedListener change = new FocusChangedListener();
+        FocusChangedListener change2 = new FocusChangedListener();
         FocusOwnershipCallback owner = new FocusOwnershipCallback();
         FocusOwnershipCallback owner2 = new FocusOwnershipCallback();
         mManager.addFocusListener(change, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
@@ -253,8 +253,8 @@
 
         Assert.assertArrayEquals(new int[0], mManager.getActiveAppTypes());
 
-        FocusChangedListerner listener = new FocusChangedListerner();
-        FocusChangedListerner listener2 = new FocusChangedListerner();
+        FocusChangedListener listener = new FocusChangedListener();
+        FocusChangedListener listener2 = new FocusChangedListener();
         FocusOwnershipCallback owner = new FocusOwnershipCallback();
         mManager.addFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
         manager2.addFocusListener(listener2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
@@ -280,8 +280,8 @@
 
     @Test
     public void testMultipleChangeListenersPerManager() throws Exception {
-        FocusChangedListerner listener = new FocusChangedListerner();
-        FocusChangedListerner listener2 = new FocusChangedListerner();
+        FocusChangedListener listener = new FocusChangedListener();
+        FocusChangedListener listener2 = new FocusChangedListener();
         FocusOwnershipCallback owner = new FocusOwnershipCallback();
         mManager.addFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
         mManager.addFocusListener(listener2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
@@ -298,14 +298,14 @@
 
         listener.reset();
         listener2.reset();
-        mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        mManager.abandonAppFocus(owner);
         assertTrue(listener.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false));
         assertTrue(listener2.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false));
     }
 
-    private class FocusChangedListerner implements CarAppFocusManager.OnAppFocusChangedListener {
+    private class FocusChangedListener implements CarAppFocusManager.OnAppFocusChangedListener {
         private int mLastChangeAppType;
         private boolean mLastChangeAppActive;
         private final Semaphore mChangeWait = new Semaphore(0);
diff --git a/tests/tests/car/src/android/car/cts/CarBluetoothTest.java b/tests/tests/car/src/android/car/cts/CarBluetoothTest.java
index aee85da..8b2dd93 100644
--- a/tests/tests/car/src/android/car/cts/CarBluetoothTest.java
+++ b/tests/tests/car/src/android/car/cts/CarBluetoothTest.java
@@ -49,10 +49,9 @@
 /**
  * Contains the tests to prove compliance with android automotive specific bluetooth requirements.
  */
-// TODO(b/146663105): Fix hidden API
-//@SmallTest
-//@RequiresDevice
-//@RunWith(AndroidJUnit4.class)
+@SmallTest
+@RequiresDevice
+@RunWith(AndroidJUnit4.class)
 public class CarBluetoothTest {
     @ClassRule
     public static final RequiredFeatureRule sRequiredFeatureRule = new RequiredFeatureRule(
@@ -165,20 +164,19 @@
             mConnected = false;
         }
     }
-// TODO(b/146663105): Fix hidden API
-/*
+
     // Automotive required profiles and meta data. Profile defaults to 'not connected' and name
     // is used in debug and error messages
     private static SparseArray<ProfileInfo> sRequiredBluetoothProfiles = new SparseArray();
     static {
-        sRequiredBluetoothProfiles.put(BluetoothProfile.A2DP_SINK,
-                new ProfileInfo("A2DP Sink")); // 11
-        sRequiredBluetoothProfiles.put(BluetoothProfile.AVRCP_CONTROLLER,
-                new ProfileInfo("AVRCP Controller")); // 12
-        sRequiredBluetoothProfiles.put(BluetoothProfile.HEADSET_CLIENT,
-                new ProfileInfo("HSP Client")); // 16
-        sRequiredBluetoothProfiles.put(BluetoothProfile.PBAP_CLIENT,
-                new ProfileInfo("PBAP Client")); // 17
+        sRequiredBluetoothProfiles.put(11,
+                new ProfileInfo("A2DP Sink")); // BluetoothProfile.A2DP_SINK
+        sRequiredBluetoothProfiles.put(12,
+                new ProfileInfo("AVRCP Controller")); // BluetoothProfile.AVRCP_CONTROLLER
+        sRequiredBluetoothProfiles.put(16,
+                new ProfileInfo("HSP Client")); // BluetoothProfile.HEADSET_CLIENT
+        sRequiredBluetoothProfiles.put(17,
+                new ProfileInfo("PBAP Client")); // BluetoothProfile.PBAP_CLIENT
     }
     private static final int MAX_PROFILES_SUPPORTED = sRequiredBluetoothProfiles.size();
 
@@ -363,5 +361,4 @@
         waitForProfileConnections();
         checkProfileConnections();
     }
-*/
 }
diff --git a/tests/tests/classloaderfactory/test-memcl/TEST_MAPPING b/tests/tests/classloaderfactory/test-memcl/TEST_MAPPING
new file mode 100644
index 0000000..1e7fc8c
--- /dev/null
+++ b/tests/tests/classloaderfactory/test-memcl/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsClassLoaderFactoryInMemoryDexClassLoaderTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/classloaderfactory/test-pathcl/TEST_MAPPING b/tests/tests/classloaderfactory/test-pathcl/TEST_MAPPING
new file mode 100644
index 0000000..0f4b263
--- /dev/null
+++ b/tests/tests/classloaderfactory/test-pathcl/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsClassLoaderFactoryPathClassLoaderTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java
deleted file mode 100644
index 2aa864c..0000000
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package android.provider.cts.contacts;
-
-import android.content.ContentValues;
-import android.net.Uri;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-
-/**
- * Make sure the provider is protected.
- *
- * Run with:
- * cts-tradefed run cts --class android.provider.cts.ContactsMetadataProviderTest < /dev/null
- */
-public class ContactsMetadataProviderTest extends AndroidTestCase {
-
-    /** The authority for the contacts metadata */
-    public static final String METADATA_AUTHORITY = "com.android.contacts.metadata";
-
-    /** A content:// style uri to the authority for the contacts metadata */
-    public static final Uri METADATA_AUTHORITY_URI = Uri.parse(
-            "content://" + METADATA_AUTHORITY);
-
-    /**
-     * The content:// style URI for this table.
-     */
-    public static final Uri CONTENT_URI = Uri.withAppendedPath(METADATA_AUTHORITY_URI,
-            "metadata_sync");
-
-    public void testCallerCheck() {
-        try {
-            getContext().getContentResolver().query(CONTENT_URI, null, null, null, null);
-            fail();
-        } catch (SecurityException e) {
-            MoreAsserts.assertContainsRegex("can't access ContactMetadataProvider", e.getMessage());
-        }
-        try {
-            getContext().getContentResolver().insert(CONTENT_URI, new ContentValues());
-            fail();
-        } catch (SecurityException e) {
-            MoreAsserts.assertContainsRegex("can't access ContactMetadataProvider", e.getMessage());
-        }
-        try {
-            getContext().getContentResolver().update(CONTENT_URI, new ContentValues(), null, null);
-            fail();
-        } catch (SecurityException e) {
-            MoreAsserts.assertContainsRegex("can't access ContactMetadataProvider", e.getMessage());
-        }
-        try {
-            getContext().getContentResolver().delete(CONTENT_URI, null, null);
-            fail();
-        } catch (SecurityException e) {
-            MoreAsserts.assertContainsRegex("can't access ContactMetadataProvider", e.getMessage());
-        }
-    }
-}
diff --git a/tests/tests/content/Android.bp b/tests/tests/content/Android.bp
index 4df740d..9b64020 100644
--- a/tests/tests/content/Android.bp
+++ b/tests/tests/content/Android.bp
@@ -63,8 +63,19 @@
     ],
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
         "BinderPermissionTestService/**/I*.aidl",
     ],
+    data: [
+        // v1/v2/v3/v4 signed version of android.appsecurity.cts.tinyapp to keep checksums stable
+        "data/CtsPkgInstallTinyAppV1.apk",
+        "data/CtsPkgInstallTinyAppV2V3V4.apk",
+        "data/CtsPkgInstallTinyAppV2V3V4.apk.idsig",
+        "data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk",
+        "data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk.idsig",
+        "data/CtsPkgInstallTinyAppV2V3V4-Verity.apk",
+        "data/CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig",
+    ],
     platform_apis: true,
     // Tag this module as a cts test artifact
     test_suites: [
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 45b6795..46bc70b 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
  * Copyright (C) 2007 The Android Open Source Project
  *
@@ -15,353 +16,468 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.cts">
+     package="android.content.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <!-- content sync tests -->
-    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
-    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.READ_SYNC_STATS" />
-    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="android.permission.SET_WALLPAPER" />
-    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.content.cts.permission.TEST_GRANTED" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
+    <uses-permission android:name="android.permission.READ_SYNC_STATS"/>
+    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="android.permission.SET_WALLPAPER"/>
+    <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.content.cts.permission.TEST_GRANTED"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
     <!-- Used for ContextTest#testCreatePackageContextAsUser -->
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
     <!-- Used for PackageManager test, don't delete this INTERNET permission -->
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <!-- Used for PackageManager test, don't delete this permission-tree -->
     <permission-tree android:name="android.content.cts.permission.TEST_DYNAMIC"
-                    android:label="Test Tree"/>
+         android:label="Test Tree"/>
 
     <!-- Used for PackageManager test, don't delete this permission-group -->
     <permission-group android:name="android.permission-group.COST_MONEY"
-            android:label="@string/permlab_costMoney"
-            android:description="@string/permdesc_costMoney"/>
+         android:label="@string/permlab_costMoney"
+         android:description="@string/permdesc_costMoney"/>
 
     <permission android:name="android.content.cts.CALL_ABROAD_PERMISSION"
-                android:label="@string/permlab_callAbroad"
-                android:description="@string/permdesc_callAbroad"
-                android:protectionLevel="normal"
-                android:permissionGroup="android.permission-group.COST_MONEY" />
+         android:label="@string/permlab_callAbroad"
+         android:description="@string/permdesc_callAbroad"
+         android:protectionLevel="normal"
+         android:permissionGroup="android.permission-group.COST_MONEY"/>
 
     <permission android:name="android.content.cts.REQUIRED_FEATURE_DEFINED"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_FEATURE_DEFINED_2"
+         android:protectionLevel="normal"/>
 
     <permission android:name="android.content.cts.REQUIRED_FEATURE_UNDEFINED"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
 
     <permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_DEFINED"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
 
     <permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED_2"
+         android:protectionLevel="normal"/>
 
     <permission android:name="android.content.cts.REQUIRED_MULTI_DENY"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2"
+                android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_MULTI_DENY_3"
+                android:protectionLevel="normal"/>
 
     <permission android:name="android.content.cts.REQUIRED_MULTI_GRANT"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_MULTI_GRANT_2"
+         android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_MULTI_GRANT_3"
+                android:protectionLevel="normal"/>
 
     <uses-permission android:name="android.content.cts.REQUIRED_FEATURE_DEFINED"
-        android:requiredFeature="android.software.cts" />
+         android:requiredFeature="android.software.cts"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_FEATURE_DEFINED_2">
+        <required-feature android:name="android.software.cts"/>
+    </uses-permission>
 
     <uses-permission android:name="android.content.cts.REQUIRED_FEATURE_UNDEFINED"
-        android:requiredFeature="android.software.cts.undefined" />
+         android:requiredFeature="android.software.cts.undefined"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_FEATURE_UNDEFINED">
+        <required-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
 
     <uses-permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_DEFINED"
-        android:requiredNotFeature="android.software.cts" />
+         android:requiredNotFeature="android.software.cts"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_DEFINED">
+        <required-not-feature android:name="android.software.cts"/>
+    </uses-permission>
 
     <uses-permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED"
-        android:requiredNotFeature="android.software.cts.undefined" />
+         android:requiredNotFeature="android.software.cts.undefined"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED_2">
+        <required-not-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
 
     <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY"
-        android:requiredFeature="android.software.cts.undefined"
-        android:requiredNotFeature="android.software.cts" />
+         android:requiredFeature="android.software.cts.undefined"
+         android:requiredNotFeature="android.software.cts"/>
 
     <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY"
-        android:requiredFeature="android.software.cts"
-        android:requiredNotFeature="android.software.cts" />
+         android:requiredFeature="android.software.cts"
+         android:requiredNotFeature="android.software.cts"/>
 
     <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY"
-        android:requiredFeature="android.software.cts.undefined"
-        android:requiredNotFeature="android.software.cts.undefined" />
+         android:requiredFeature="android.software.cts.undefined"
+         android:requiredNotFeature="android.software.cts.undefined"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-feature android:name="android.software.cts.undefined"/>
+        <required-not-feature android:name="android.software.cts"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-feature android:name="android.software.cts"/>
+        <required-not-feature android:name="android.software.cts"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-feature android:name="android.software.cts.undefined"/>
+        <required-not-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-feature android:name="android.software.cts"/>
+        <required-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-not-feature android:name="android.software.cts"/>
+        <required-not-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-feature android:name="android.software.cts"/>
+        <required-feature android:name="android.software.cts.another.undefined"/>
+        <required-not-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_3"
+         android:requiredFeature="android.software.cts">
+        <required-not-feature android:name="android.software.cts"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_3"
+         android:requiredFeature="android.software.cts.undefined">
+        <required-not-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_3"
+         android:requiredNotFeature="android.software.cts">
+        <required-feature android:name="android.software.cts"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_3"
+         android:requiredNotFeature="android.software.cts.undefined">
+        <required-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
 
     <uses-permission android:name="android.content.cts.REQUIRED_MULTI_GRANT"
-        android:requiredFeature="android.software.cts"
-        android:requiredNotFeature="android.software.cts.undefined" />
+         android:requiredFeature="android.software.cts"
+         android:requiredNotFeature="android.software.cts.undefined"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_GRANT_2">
+        <required-feature android:name="android.software.cts"/>
+        <required-not-feature android:name="android.software.cts.undefined"/>
+        <required-not-feature android:name="android.software.cts.another.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_GRANT_3"
+         android:requiredFeature="android.software.cts"
+         android:requiredNotFeature="android.software.cts.undefined">
+        <required-not-feature android:name="android.software.cts.another.undefined"/>
+    </uses-permission>
 
     <permission android:name="android.content.cts.SIGNATURE_PERMISSION"
-        android:protectionLevel="signature" />
+         android:protectionLevel="signature"/>
 
-    <uses-permission android:name="android.content.cts.SIGNATURE_PERMISSION" />
+    <uses-permission android:name="android.content.cts.SIGNATURE_PERMISSION"/>
 
     <!-- Used for PackageManager test, don't delete! -->
     <uses-configuration/>
-    <uses-feature android:name="android.hardware.camera" />
-    <uses-feature android:glEsVersion="0x00020000" />
+    <uses-feature android:name="android.hardware.camera"/>
+    <uses-feature android:glEsVersion="0x00020000"/>
     <feature-group/>
     <feature-group>
-        <uses-feature android:glEsVersion="0x00030000" />
-        <uses-feature android:name="android.hardware.location" />
+        <uses-feature android:glEsVersion="0x00030000"/>
+        <uses-feature android:name="android.hardware.location"/>
     </feature-group>
     <feature-group>
-        <uses-feature android:glEsVersion="0x00010001" />
-        <uses-feature android:name="android.hardware.camera" />
+        <uses-feature android:glEsVersion="0x00010001"/>
+        <uses-feature android:name="android.hardware.camera"/>
     </feature-group>
 
     <application android:label="Android TestCase"
-                android:icon="@drawable/size_48x48"
-                android:maxRecents="1"
-                android:multiArch="true"
-                android:name="android.content.cts.MockApplication"
-                android:supportsRtl="true"
-                android:appCategory="productivity">
-        <activity android:name="android.content.cts.MockActivity">
+         android:icon="@drawable/size_48x48"
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:name="android.content.cts.MockApplication"
+         android:supportsRtl="true"
+         android:appCategory="productivity">
+        <activity android:name="android.content.cts.MockActivity"
+             android:exported="true">
             <meta-data android:name="android.app.alias"
-                android:resource="@xml/alias" />
+                 android:resource="@xml/alias"/>
             <meta-data android:name="android.app.intent.filter"
-                android:resource="@xml/intentfilter" />
+                 android:resource="@xml/intentfilter"/>
             <meta-data android:name="android.app.intent"
-                       android:resource="@xml/intent" />
+                 android:resource="@xml/intent"/>
             <intent-filter>
-                <action android:name="android.content.cts.action.TEST_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.content.cts.category.TEST_CATEGORY" />
+                <action android:name="android.content.cts.action.TEST_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.content.cts.category.TEST_CATEGORY"/>
             </intent-filter>
         </activity>
 
         <activity-alias android:name="android.content.cts.MockActivity2"
-                android:targetActivity="android.content.cts.MockActivity">
+             android:targetActivity="android.content.cts.MockActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.cts.action.TEST_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.content.cts.action.TEST_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity-alias>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <service android:name="android.content.cts.MockContextService" />
+        <service android:name="android.content.cts.MockContextService"/>
         <activity android:name=".content.ContextCtsActivity"
-            android:label="ContextCtsActivity">
+             android:label="ContextCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <receiver android:name="android.content.cts.MockReceiverFirst">
+        <receiver android:name="android.content.cts.MockReceiverFirst"
+             android:exported="true">
             <intent-filter android:priority="3">
-                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT" />
+                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT"/>
             </intent-filter>
         </receiver>
-        <receiver android:name="android.content.cts.MockReceiverAbort">
+        <receiver android:name="android.content.cts.MockReceiverAbort"
+             android:exported="true">
             <intent-filter android:priority="2">
-                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT" />
+                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT"/>
             </intent-filter>
         </receiver>
         <receiver android:name="android.content.cts.MockReceiver"
-                android:permission="android.content.cts.SIGNATURE_PERMISSION">
+             android:permission="android.content.cts.SIGNATURE_PERMISSION"
+             android:exported="true">
             <intent-filter android:priority="1">
-                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_MOCKTEST" />
-                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT" />
-                <action android:name="android.content.cts.ContextTest.BROADCAST_TESTORDER" />
+                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_MOCKTEST"/>
+                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT"/>
+                <action android:name="android.content.cts.ContextTest.BROADCAST_TESTORDER"/>
             </intent-filter>
         </receiver>
 
         <!-- Receiver that will be explicitly disabled at runtime -->
         <receiver android:name="android.content.cts.MockReceiverDisableable"
-                android:enabled="true">
+             android:enabled="true"
+             android:exported="true">
             <intent-filter android:priority="1">
-                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_DISABLED" />
+                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_DISABLED"/>
             </intent-filter>
         </receiver>
 
         <activity android:name="android.content.cts.AvailableIntentsActivity"
-            android:label="AvailableIntentsActivity">
+             android:label="AvailableIntentsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <!--Test for PackageManager-->
         <activity android:name="android.content.pm.cts.TestPmActivity"
-                android:icon="@drawable/start"
-                android:launchMode="singleTop">
+             android:icon="@drawable/start"
+             android:launchMode="singleTop"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.PMTEST" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.PMTEST"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
-            <meta-data android:name="android.content.pm.cts.xmltest" android:resource="@xml/pm_test" />
+            <meta-data android:name="android.content.pm.cts.xmltest"
+                 android:resource="@xml/pm_test"/>
         </activity>
-        <activity android:name="android.content.pm.cts.TestPmCompare">
+        <activity android:name="android.content.pm.cts.TestPmCompare"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.INFO" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.INFO"/>
             </intent-filter>
         </activity>
         <!--Test for PackageManager-->
         <service android:name="android.content.pm.cts.TestPmService"
-            android:permission="android.content.cts.CALL_ABROAD_PERMISSION">
+             android:permission="android.content.cts.CALL_ABROAD_PERMISSION"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.cts.activity.PMTEST_SERVICE" />
+                <action android:name="android.content.pm.cts.activity.PMTEST_SERVICE"/>
             </intent-filter>
         </service>
         <!--Test for PackageManager-->
-        <receiver android:name="android.content.pm.cts.PmTestReceiver">
+        <receiver android:name="android.content.pm.cts.PmTestReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.cts.PackageManagerTest.PMTEST_RECEIVER" />
+                <action android:name="android.content.pm.cts.PackageManagerTest.PMTEST_RECEIVER"/>
             </intent-filter>
         </receiver>
 
         <activity android:name="android.content.pm.cts.LauncherMockActivity"
-                  android:enabled="true">
+             android:enabled="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.HOME" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.HOME"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
         <!--Used by test for LauncherApps-->
         <activity-alias android:name="android.content.pm.cts.MockActivity_Disabled"
-            android:targetActivity="android.content.cts.MockActivity"
-            android:enabled="false">
+             android:targetActivity="android.content.cts.MockActivity"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.cts.action.TEST_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.content.cts.action.TEST_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity-alias>
 
         <!-- Used for PackageManager test, don't delete this MockContentProvider provider -->
-        <provider android:name="android.content.cts.MockContentProvider" android:authorities="ctstest"
-            android:multiprocess="false" />
+        <provider android:name="android.content.cts.MockContentProvider"
+             android:authorities="ctstest"
+             android:multiprocess="false"/>
         <provider android:name="android.content.cts.MockSRSProvider"
-                  android:authorities="android.content.cts.MockSRSProvider"
-                  android:exported="false"
-                  android:multiprocess="false" />
+             android:authorities="android.content.cts.MockSRSProvider"
+             android:exported="false"
+             android:multiprocess="false"/>
         <provider android:name="android.content.cts.DummyProvider"
-            android:authorities="android.content.cts.dummyprovider"
-            android:multiprocess="true" />
+             android:authorities="android.content.cts.dummyprovider"
+             android:multiprocess="true"/>
         <provider android:name="android.content.cts.MockRemoteContentProvider"
-            android:authorities="remotectstest"
-            android:process=":remoteprovider" android:multiprocess="false" />
+             android:authorities="remotectstest"
+             android:process=":remoteprovider"
+             android:multiprocess="false"/>
         <provider android:name="androidx.core.content.FileProvider"
-            android:authorities="android.content.cts.fileprovider"
-            android:grantUriPermissions="true">
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/file_paths" />
+             android:authorities="android.content.cts.fileprovider"
+             android:grantUriPermissions="true">
+            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+                 android:resource="@xml/file_paths"/>
         </provider>
 
         <provider android:name="android.content.cts.TestPagingContentProvider"
-            android:authorities="android.content.cts.testpagingprovider"
-            android:process=":testpagingprovider"
-            android:multiprocess="false" />
+             android:authorities="android.content.cts.testpagingprovider"
+             android:process=":testpagingprovider"
+             android:multiprocess="false"/>
 
         <provider android:name="android.content.cts.MockBuggyProvider"
-                  android:authorities="android.content.cts.mockbuggyprovider"
-                  android:process=":mockbuggyprovider"
-                  android:multiprocess="false" />
+             android:authorities="android.content.cts.mockbuggyprovider"
+             android:process=":mockbuggyprovider"
+             android:multiprocess="false"/>
 
-        <service android:name="android.content.cts.MockService" />
+        <service android:name="android.content.cts.MockService"/>
 
-        <service android:name="android.content.cts.MockSyncAdapterService" android:exported="true">
+        <service android:name="android.content.cts.MockSyncAdapterService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.SyncAdapter" />
+                <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
 
             <meta-data android:name="android.content.SyncAdapter"
-                       android:resource="@xml/syncadapter" />
+                 android:resource="@xml/syncadapter"/>
         </service>
 
-        <service android:name="android.content.cts.MockAccountService" android:exported="true"
-                 >
+        <service android:name="android.content.cts.MockAccountService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
 
             <meta-data android:name="android.accounts.AccountAuthenticator"
-                       android:resource="@xml/authenticator" />
+                 android:resource="@xml/authenticator"/>
         </service>
 
         <activity android:name="android.content.cts.ClipboardManagerListenerActivity"/>
 
         <activity android:name="android.content.cts.ImageCaptureActivity"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.action.IMAGE_CAPTURE" />
-                <action android:name="android.media.action.IMAGE_CAPTURE_SECURE" />
-                <action android:name="android.media.action.VIDEO_CAPTURE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.media.action.IMAGE_CAPTURE"/>
+                <action android:name="android.media.action.IMAGE_CAPTURE_SECURE"/>
+                <action android:name="android.media.action.VIDEO_CAPTURE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.content.cts.ReadableFileReceiverActivity"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SEND_MULTIPLE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SEND_MULTIPLE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <provider
-                android:name="android.content.cts.CursorWindowContentProvider"
-                android:authorities="cursorwindow.provider"
-                android:exported="true"
-                android:process=":providerProcess">
+        <provider android:name="android.content.cts.CursorWindowContentProvider"
+             android:authorities="cursorwindow.provider"
+             android:exported="true"
+             android:process=":providerProcess">
         </provider>
 
         <activity android:name="com.android.cts.content.StubActivity"/>
 
-        <service android:name="com.android.cts.content.NotAlwaysSyncableSyncService">
+        <service android:name="com.android.cts.content.NotAlwaysSyncableSyncService"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
             <meta-data android:name="android.content.SyncAdapter"
-                android:resource="@xml/not_always_syncable_account_access_adapter" />
+                 android:resource="@xml/not_always_syncable_account_access_adapter"/>
         </service>
 
-        <service android:name="com.android.cts.content.AlwaysSyncableSyncService">
+        <service android:name="com.android.cts.content.AlwaysSyncableSyncService"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
             <meta-data android:name="android.content.SyncAdapter"
-                android:resource="@xml/always_syncable_account_access_adapter" />
+                 android:resource="@xml/always_syncable_account_access_adapter"/>
         </service>
 
-	<activity android:name="com.android.cts.content.StubCameraIntentHandlerActivity">
+	<activity android:name="com.android.cts.content.StubCameraIntentHandlerActivity"
+    	 android:exported="true">
            <intent-filter>
-                <action android:name="android.media.action.IMAGE_CAPTURE" />
-                <action android:name="android.media.action.IMAGE_CAPTURE_SECURE" />
-                <action android:name="android.media.action.VIDEO_CAPTURE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.media.action.IMAGE_CAPTURE"/>
+                <action android:name="android.media.action.IMAGE_CAPTURE_SECURE"/>
+                <action android:name="android.media.action.VIDEO_CAPTURE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
 	</activity>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.content.cts"
-                     android:label="CTS tests of android.content">
+         android:targetPackage="android.content.cts"
+         android:label="CTS tests of android.content">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
     <instrumentation android:name="android.content.pm.cts.TestPmInstrumentation"
-        android:targetPackage="android"
-        android:label="PackageManager Instrumentation Test" />
+         android:targetPackage="android"
+         android:label="PackageManager Instrumentation Test"/>
 </manifest>
-
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index 7c0f3ac..718ee40 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -54,6 +54,8 @@
         <option name="push-file" key="HelloWorld5_xxhdpi-v4.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld5_xxhdpi-v4.apk.idsig" />
         <option name="push-file" key="HelloWorld5_xxxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_xxxhdpi-v4.apk" />
         <option name="push-file" key="HelloWorld5_xxxhdpi-v4.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld5_xxxhdpi-v4.apk.idsig" />
+        <option name="push-file" key="HelloWorld5Profileable.apk" value="/data/local/tmp/cts/content/HelloWorld5Profileable.apk" />
+        <option name="push-file" key="HelloWorld5Profileable.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld5Profileable.apk.idsig" />
         <option name="push-file" key="HelloWorld7.apk" value="/data/local/tmp/cts/content/HelloWorld7.apk" />
         <option name="push-file" key="HelloWorld7.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld7.apk.idsig" />
         <option name="push-file" key="HelloWorld7_hdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_hdpi-v4.apk" />
@@ -66,7 +68,17 @@
         <option name="push-file" key="HelloWorld7_xxhdpi-v4.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld7_xxhdpi-v4.apk.idsig" />
         <option name="push-file" key="HelloWorld7_xxxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_xxxhdpi-v4.apk" />
         <option name="push-file" key="HelloWorld7_xxxhdpi-v4.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld7_xxxhdpi-v4.apk.idsig" />
-      </target_preparer>
+        <option name="push-file" key="HelloWorldShell.apk" value="/data/local/tmp/cts/content/HelloWorldShell.apk" />
+        <option name="push-file" key="HelloWorldShell.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldShell.apk.idsig" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV1.apk" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV1.apk" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4.apk" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4.apk" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4.apk.idsig" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4.apk.idsig" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk.idsig" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4.apk-Sha512withEC.idsig" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4-Verity.apk" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4-Verity.apk" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig" />
+
+    </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
index 67d20f9..acb5e66 100644
--- a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
@@ -15,27 +15,27 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.content">
+     package="com.android.cts.content">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name=".StubActivity"/>
 
-        <service android:name=".AlwaysSyncableSyncService">
+        <service android:name=".AlwaysSyncableSyncService"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
             <meta-data android:name="android.content.SyncAdapter"
-                   android:resource="@xml/syncadapter" />
+                 android:resource="@xml/syncadapter"/>
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.content" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.content"/>
 
 </manifest>
diff --git a/tests/tests/content/HelloWorldApp/Android.bp b/tests/tests/content/HelloWorldApp/Android.bp
index b303c78..09b232b 100644
--- a/tests/tests/content/HelloWorldApp/Android.bp
+++ b/tests/tests/content/HelloWorldApp/Android.bp
@@ -46,6 +46,21 @@
 
 //-----------------------------------------------------------
 android_test {
+    name: "HelloWorld5Profileable",
+    defaults: ["hello_world_defaults"],
+    srcs: ["src5/**/*.java"],
+    manifest: "AndroidManifestProfileable.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
     name: "HelloWorld7",
     defaults: ["hello_world_defaults"],
     srcs: ["src7/**/*.java"],
@@ -56,3 +71,18 @@
     ],
     v4_signature: true,
 }
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldShell",
+    defaults: ["hello_world_defaults"],
+    srcs: ["src_shell/**/*.java"],
+    manifest: "AndroidManifestShell.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifest.xml b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
index f195701..875c27a 100644
--- a/tests/tests/content/HelloWorldApp/AndroidManifest.xml
+++ b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
@@ -1,25 +1,25 @@
 <?xml version="1.0" encoding="utf-8"?>
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.helloworld">
+     package="com.example.helloworld">
 
-    <application
-        android:allowBackup="true"
-        android:debuggable="true"
-        android:icon="@mipmap/ic_launcher"
-        android:label="@string/app_name"
-        android:roundIcon="@mipmap/ic_launcher_round"
-        android:supportsRtl="true"
-        android:theme="@style/AppTheme">
-        <activity
-            android:name=".MainActivity"
-            android:label="@string/app_name"
-            android:theme="@style/AppTheme.NoActionBar">
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
 
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestProfileable.xml b/tests/tests/content/HelloWorldApp/AndroidManifestProfileable.xml
new file mode 100644
index 0000000..0410a4b
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestProfileable.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.example.helloworld">
+
+    <application android:allowBackup="true"
+         android:debuggable="false"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <profileable android:shell="true"/>
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestShell.xml b/tests/tests/content/HelloWorldApp/AndroidManifestShell.xml
new file mode 100644
index 0000000..c42546a
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestShell.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.shell">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <profileable android:shell="true"/>
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/src_shell/com/android/shell/MainActivity.java b/tests/tests/content/HelloWorldApp/src_shell/com/android/shell/MainActivity.java
new file mode 100644
index 0000000..d771f8d
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/src_shell/com/android/shell/MainActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+public class MainActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        System.exit(1);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.menu_main, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle action bar item clicks here. The action bar will
+        // automatically handle clicks on the Home/Up button, so long
+        // as you specify a parent activity in AndroidManifest.xml.
+        int id = item.getItemId();
+
+        //noinspection SimplifiableIfStatement
+        if (id == R.id.action_settings) {
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
index a0dee84..8423733 100644
--- a/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
+++ b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
@@ -15,31 +15,28 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.stub">
+     package="com.android.cts.stub">
 
     <application>
-        <service
-                android:name=".StubAuthenticator">
+        <service android:name=".StubAuthenticator"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/authenticator" />
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/authenticator"/>
         </service>
 
-        <provider
-            android:name=".StubProvider"
-            android:authorities="com.android.cts.stub.provider"
-            android:exported="true"
-            android:syncable="true">
+        <provider android:name=".StubProvider"
+             android:authorities="com.android.cts.stub.provider"
+             android:exported="true"
+             android:syncable="true">
         </provider>
 
-        <provider
-            android:name=".StubProvider2"
-            android:authorities="com.android.cts.stub.provider2"
-            android:exported="true"
-            android:syncable="true">
+        <provider android:name=".StubProvider2"
+             android:authorities="com.android.cts.stub.provider2"
+             android:exported="true"
+             android:syncable="true">
         </provider>
 
     </application>
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV1.apk b/tests/tests/content/data/CtsPkgInstallTinyAppV1.apk
new file mode 100644
index 0000000..6b6d3d6
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV1.apk
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk
new file mode 100644
index 0000000..b692269
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk.idsig b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk.idsig
new file mode 100644
index 0000000..311a2ae
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk.idsig
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk
new file mode 100644
index 0000000..90353f0
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig
new file mode 100644
index 0000000..63fabed
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk
new file mode 100644
index 0000000..ec9c138
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk.idsig b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk.idsig
new file mode 100644
index 0000000..524ef76
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk.idsig
Binary files differ
diff --git a/tests/tests/content/data/readme.txt b/tests/tests/content/data/readme.txt
new file mode 100644
index 0000000..140c5ce
--- /dev/null
+++ b/tests/tests/content/data/readme.txt
@@ -0,0 +1,19 @@
+Fixed APKs used in ChecksumsTest.java.
+Has to be submitted instead of built to keep hashes constant.
+
+Generation of these apks was performed using the `apksigner` command-line tool,
+which lives at `tools/apksig/src/apksigner/java/com/android/apksigner/` in the
+android source tree.  Please refer to the usage instructions there for how to
+sign APKs using different keystores, providers, etc.
+
+Source app:
+cts/hostsidetests/appsecurity/test-apps/tinyapp
+
+Use this command to re-generate the apk and v4 signature file:
+apksigner sign --v2-signing-enabled false --v3-signing-enabled false --v4-signing-enabled false --key cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.pk8 --cert cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.x509.pem -out cts/tests/tests/content/data/CtsPkgInstallTinyAppV1.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
+apksigner sign --v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled --key cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.pk8 --cert cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.x509.pem -out cts/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
+apksigner sign --v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled --key cts/hostsidetests/appsecurity/certs/pkgsigverify/ec-p384.pk8 --cert cts/hostsidetests/appsecurity/certs/pkgsigverify/ec-p384.x509.pem -out cts/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
+apksigner sign --v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled --verity-enabled --key cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.pk8 --cert cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.x509.pem -out cts/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
+
+!Please note that all hardcoded hashes in ChecksumsTest.java will have to be changed!
+Use md5sum, sha1sum, sha256sum, sha512sum to regenerate full apk hashes.
diff --git a/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java b/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java
new file mode 100644
index 0000000..55b4c32
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.cts;
+
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
+import static android.content.pm.Checksum.TYPE_WHOLE_MD5;
+import static android.content.pm.Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA1;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
+import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
+import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
+import static android.content.pm.PackageManager.TRUST_ALL;
+import static android.content.pm.PackageManager.TRUST_NONE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ApkChecksum;
+import android.content.pm.Checksum;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.Session;
+import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.platform.test.annotations.AppModeFull;
+import android.util.ExceptionUtils;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+import com.android.server.pm.PackageManagerShellCommandDataLoader;
+import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class ChecksumsTest {
+    private static final String CTS_PACKAGE_NAME = "android.content.cts";
+    private static final String V2V3_PACKAGE_NAME = "android.content.cts";
+    private static final String V4_PACKAGE_NAME = "com.example.helloworld";
+    private static final String FIXED_PACKAGE_NAME = "android.appsecurity.cts.tinyapp";
+
+    private static final String TEST_APK_PATH = "/data/local/tmp/cts/content/";
+
+    private static final String TEST_V4_APK = "HelloWorld5.apk";
+    private static final String TEST_V4_SPLIT0 = "HelloWorld5_hdpi-v4.apk";
+    private static final String TEST_V4_SPLIT1 = "HelloWorld5_mdpi-v4.apk";
+    private static final String TEST_V4_SPLIT2 = "HelloWorld5_xhdpi-v4.apk";
+    private static final String TEST_V4_SPLIT3 = "HelloWorld5_xxhdpi-v4.apk";
+    private static final String TEST_V4_SPLIT4 = "HelloWorld5_xxxhdpi-v4.apk";
+
+    private static final String TEST_FIXED_APK = "CtsPkgInstallTinyAppV2V3V4.apk";
+    private static final String TEST_FIXED_APK_V1 = "CtsPkgInstallTinyAppV1.apk";
+    private static final String TEST_FIXED_APK_SHA512 =
+            "CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk";
+    private static final String TEST_FIXED_APK_VERITY = "CtsPkgInstallTinyAppV2V3V4-Verity.apk";
+
+    private static final String TEST_FIXED_APK_V2_SHA256 =
+            "1eec9e86e322b8d7e48e255fc3f2df2dbc91036e63982ff9850597c6a37bbeb3";
+    private static final String TEST_FIXED_APK_SHA256 =
+            "91aa30c1ce8d0474052f71cb8210691d41f534989c5521e27e794ec4f754c5ef";
+    private static final String TEST_FIXED_APK_MD5 = "c19868da017dc01467169f8ea7c5bc57";
+    private static final Checksum[] TEST_FIXED_APK_DIGESTS = new Checksum[]{new Checksum(
+            TYPE_WHOLE_SHA256, hexStringToBytes(TEST_FIXED_APK_SHA256)), new Checksum(
+            TYPE_WHOLE_MD5, hexStringToBytes(TEST_FIXED_APK_MD5))};
+
+    private static final int ALL_CHECKSUMS =
+            TYPE_WHOLE_MERKLE_ROOT_4K_SHA256 | TYPE_WHOLE_MD5 | TYPE_WHOLE_SHA1 | TYPE_WHOLE_SHA256
+                    | TYPE_WHOLE_SHA512
+                    | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256 | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
+
+    private static UiAutomation getUiAutomation() {
+        return InstrumentationRegistry.getInstrumentation().getUiAutomation();
+    }
+
+    private static PackageManager getPackageManager() {
+        return InstrumentationRegistry.getContext().getPackageManager();
+    }
+
+    private static PackageInstaller getPackageInstaller() {
+        return getPackageManager().getPackageInstaller();
+    }
+
+    @Rule
+    public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
+
+    @Before
+    public void onBefore() throws Exception {
+        uninstallPackageSilently(V4_PACKAGE_NAME);
+        assertFalse(isAppInstalled(V4_PACKAGE_NAME));
+        uninstallPackageSilently(FIXED_PACKAGE_NAME);
+        assertFalse(isAppInstalled(FIXED_PACKAGE_NAME));
+    }
+
+    @After
+    public void onAfter() throws Exception {
+        uninstallPackageSilently(V4_PACKAGE_NAME);
+        assertFalse(isAppInstalled(V4_PACKAGE_NAME));
+        uninstallPackageSilently(FIXED_PACKAGE_NAME);
+        assertFalse(isAppInstalled(FIXED_PACKAGE_NAME));
+    }
+
+    @Test
+    public void testDefaultChecksums() throws Exception {
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(V2V3_PACKAGE_NAME, true, 0, TRUST_NONE, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+    }
+
+    @Test
+    public void testSplitsDefaultChecksums() throws Exception {
+        installSplits(new String[]{TEST_V4_APK, TEST_V4_SPLIT0, TEST_V4_SPLIT1, TEST_V4_SPLIT2,
+                TEST_V4_SPLIT3, TEST_V4_SPLIT4});
+        assertTrue(isAppInstalled(V4_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(V4_PACKAGE_NAME, true, 0, TRUST_NONE, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 6);
+        // v2/v3 signature use 1M merkle tree.
+        assertEquals(checksums[0].getSplitName(), null);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[1].getSplitName(), "config.hdpi");
+        assertEquals(checksums[1].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[2].getSplitName(), "config.mdpi");
+        assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[3].getSplitName(), "config.xhdpi");
+        assertEquals(checksums[3].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[4].getSplitName(), "config.xxhdpi");
+        assertEquals(checksums[4].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[5].getSplitName(), "config.xxxhdpi");
+        assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+    }
+
+    @Test
+    public void testFixedDefaultChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        // v2/v3 signature use 1M merkle tree.
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    @Test
+    public void testFixedV1DefaultChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK_V1);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 0);
+    }
+
+    @Test
+    public void testFixedSha512DefaultChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK_SHA512);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        // v2/v3 signature use 1M merkle tree.
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512);
+        assertEquals(bytesToHexString(checksums[0].getValue()),
+                "6b866e8a54a3e358dfc20007960fb96123845f6c6d6c45f5fddf88150d71677f"
+                        + "4c3081a58921c88651f7376118aca312cf764b391cdfb8a18c6710f9f27916a0");
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    @Test
+    public void testFixedVerityDefaultChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK_VERITY);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        // No usable hashes as verity-in-v2-signature does not cover the whole file.
+        assertEquals(checksums.length, 0);
+    }
+
+    @LargeTest
+    @Test
+    public void testAllChecksums() throws Exception {
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(V2V3_PACKAGE_NAME, true, ALL_CHECKSUMS, TRUST_NONE,
+                receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 7);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_MD5);
+        assertEquals(checksums[2].getType(), TYPE_WHOLE_SHA1);
+        assertEquals(checksums[3].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA512);
+        assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[6].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512);
+    }
+
+    @LargeTest
+    @Test
+    public void testFixedAllChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, ALL_CHECKSUMS, TRUST_NONE,
+                receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 7);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()),
+                "90553b8d221ab1b900b242a93e4cc659ace3a2ff1d5c62e502488b385854e66a");
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[2].getType(), TYPE_WHOLE_SHA1);
+        assertEquals(bytesToHexString(checksums[2].getValue()),
+                "331eef6bc57671de28cbd7e32089d047285ade6a");
+        assertEquals(checksums[3].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[3].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA512);
+        assertEquals(bytesToHexString(checksums[4].getValue()),
+                "b59467fe578ebc81974ab3aaa1e0d2a76fef3e4ea7212a6f2885cec1af5253571"
+                        + "1e2e94496224cae3eba8dc992144ade321540ebd458ec5b9e6a4cc51170e018");
+        assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[5].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertEquals(checksums[6].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512);
+        assertEquals(bytesToHexString(checksums[6].getValue()),
+                "ef80a8630283f60108e8557c924307d0ccdfb6bbbf2c0176bd49af342f43bc84"
+                        + "5f2888afcb71524196dda0d6dd16a6a3292bb75b431b8ff74fb60d796e882f80");
+    }
+
+    @LargeTest
+    @Test
+    public void testFixedV1AllChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK_V1);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, ALL_CHECKSUMS, TRUST_NONE,
+                receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 5);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()),
+                "1e8f831ef35257ca30d11668520aaafc6da243e853531caabc3b7867986f8886");
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[1].getValue()), "78e51e8c51e4adc6870cd71389e0f3db");
+        assertEquals(checksums[2].getType(), TYPE_WHOLE_SHA1);
+        assertEquals(bytesToHexString(checksums[2].getValue()),
+                "f6654505f2274fd9bfc098b660cdfdc2e4da6d53");
+        assertEquals(checksums[3].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[3].getValue()),
+                "43755d36ec944494f6275ee92662aca95079b3aa6639f2d35208c5af15adff78");
+        assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA512);
+        assertEquals(bytesToHexString(checksums[4].getValue()),
+                "030fc815a4957c163af2bc6f30dd5b48ac09c94c25a824a514609e1476f91421"
+                        + "e2c8b6baa16ef54014ad6c5b90c37b26b0f5c8aeb01b63a1db2eca133091c8d1");
+    }
+
+    @Test
+    public void testDefaultIncrementalChecksums() throws Exception {
+        if (!checkIncrementalDeliveryFeature()) {
+            return;
+        }
+        installPackageIncrementally(TEST_V4_APK);
+        assertTrue(isAppInstalled(V4_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(V4_PACKAGE_NAME, true, 0, TRUST_NONE, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+    }
+
+    @Test
+    public void testFixedDefaultIncrementalChecksums() throws Exception {
+        if (!checkIncrementalDeliveryFeature()) {
+            return;
+        }
+        installPackageIncrementally(TEST_FIXED_APK);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()),
+                "90553b8d221ab1b900b242a93e4cc659ace3a2ff1d5c62e502488b385854e66a");
+    }
+
+    @LargeTest
+    @Test
+    public void testFixedAllIncrementalChecksums() throws Exception {
+        if (!checkIncrementalDeliveryFeature()) {
+            return;
+        }
+        installPackageIncrementally(TEST_FIXED_APK);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, ALL_CHECKSUMS, TRUST_NONE,
+                receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 7);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()),
+                "90553b8d221ab1b900b242a93e4cc659ace3a2ff1d5c62e502488b385854e66a");
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[2].getType(), TYPE_WHOLE_SHA1);
+        assertEquals(bytesToHexString(checksums[2].getValue()),
+                "331eef6bc57671de28cbd7e32089d047285ade6a");
+        assertEquals(checksums[3].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[3].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA512);
+        assertEquals(bytesToHexString(checksums[4].getValue()),
+                "b59467fe578ebc81974ab3aaa1e0d2a76fef3e4ea7212a6f2885cec1af5253571"
+                        + "1e2e94496224cae3eba8dc992144ade321540ebd458ec5b9e6a4cc51170e018");
+        assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[5].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertEquals(checksums[6].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512);
+        assertEquals(bytesToHexString(checksums[6].getValue()),
+                "ef80a8630283f60108e8557c924307d0ccdfb6bbbf2c0176bd49af342f43bc84"
+                        + "5f2888afcb71524196dda0d6dd16a6a3292bb75b431b8ff74fb60d796e882f80");
+    }
+
+    @Test
+    public void testInstallerChecksumsTrustNone() throws Exception {
+        installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[0].getInstallerPackageName());
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerChecksumsTrustAll() throws Exception {
+        installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_ALL, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        // v2/v3+installer provided.
+        assertEquals(checksums.length, 3);
+
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[0].getSplitName(), null);
+        assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertNotNull(checksums[0].getInstallerCertificate());
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[1].getSplitName(), null);
+        assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertNotNull(checksums[1].getInstallerCertificate());
+        assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[2].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertEquals(checksums[2].getSplitName(), null);
+        assertNull(checksums[2].getInstallerPackageName());
+        assertNull(checksums[2].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerChecksumsTrustInstaller() throws Exception {
+        installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
+
+        // Using the installer's certificate(s).
+        PackageManager pm = getPackageManager();
+        PackageInfo packageInfo = pm.getPackageInfo(CTS_PACKAGE_NAME, GET_SIGNING_CERTIFICATES);
+        final List<Certificate> signatures = convertSignaturesToCertificates(
+                packageInfo.signingInfo.getApkContentsSigners());
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, signatures, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 3);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[0].getSplitName(), null);
+        assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertNotNull(checksums[0].getInstallerCertificate());
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[1].getSplitName(), null);
+        assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertNotNull(checksums[1].getInstallerCertificate());
+        assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[2].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertEquals(checksums[2].getSplitName(), null);
+        assertNull(checksums[2].getInstallerPackageName());
+        assertNull(checksums[2].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerChecksumsTrustWrongInstaller() throws Exception {
+        installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
+
+        // Using certificates from a security app, not the installer (us).
+        PackageManager pm = getPackageManager();
+        PackageInfo packageInfo = pm.getPackageInfo(FIXED_PACKAGE_NAME, GET_SIGNING_CERTIFICATES);
+        final List<Certificate> signatures = convertSignaturesToCertificates(
+                packageInfo.signingInfo.getApkContentsSigners());
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, signatures, receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[0].getInstallerPackageName());
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerChecksumsTrustAllWrongName() throws Exception {
+        CommitIntentReceiver.checkFailure(
+                installApkWithChecksums(TEST_FIXED_APK, "apk", "wrong_name",
+                        TEST_FIXED_APK_DIGESTS),
+                "INSTALL_FAILED_SESSION_INVALID: Invalid checksum name(s): wrong_name");
+    }
+
+    @Test
+    public void testInstallerChecksumsUpdate() throws Exception {
+        Checksum[] digests_base = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+                "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e")),
+                new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("dd93e23bb8cdab0382fdca0d21a4f1cb"))};
+        Checksum[] digests_split0 = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+                "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e")),
+                new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("f6430e1b795ce2658c49e68d15316b2d"))};
+        Checksum[] digests_split1 = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+                "f16898f43990c14585a900eda345c3a236c6224f63920d69cfe8a7afbc0c0ccf")),
+                new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("d1f4b00d034994663e84f907fe4bb664"))};
+
+        // Original package checksums: base + split0.
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+
+            writeFileToSession(session, "hw5", TEST_V4_APK);
+            session.addChecksums("hw5", Arrays.asList(digests_base));
+
+            writeFileToSession(session, "hw5_split0", TEST_V4_SPLIT0);
+            session.addChecksums("hw5_split0", Arrays.asList(digests_split0));
+
+            CommitIntentReceiver receiver = new CommitIntentReceiver();
+            session.commit(receiver.getIntentSender());
+            CommitIntentReceiver.checkSuccess(receiver.getResult());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+
+        {
+            LocalIntentReceiver receiver = new LocalIntentReceiver();
+            PackageManager pm = getPackageManager();
+            pm.requestChecksums(V4_PACKAGE_NAME, true, 0, TRUST_ALL, receiver.getIntentSender());
+            ApkChecksum[] checksums = receiver.getResult();
+            assertNotNull(checksums);
+            assertEquals(checksums.length, 6);
+            // base
+            assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[0].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[0].getValue()),
+                    "dd93e23bb8cdab0382fdca0d21a4f1cb");
+            assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertNotNull(checksums[0].getInstallerCertificate());
+            assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[1].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[1].getValue()),
+                    "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e");
+            assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertNotNull(checksums[1].getInstallerCertificate());
+            assertEquals(checksums[2].getSplitName(), null);
+            assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[2].getInstallerPackageName());
+            assertNull(checksums[2].getInstallerCertificate());
+            // split0
+            assertEquals(checksums[3].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[3].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[3].getValue()),
+                    "f6430e1b795ce2658c49e68d15316b2d");
+            assertEquals(checksums[3].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertNotNull(checksums[3].getInstallerCertificate());
+            assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[4].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[4].getValue()),
+                    "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e");
+            assertEquals(checksums[4].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertNotNull(checksums[4].getInstallerCertificate());
+            assertEquals(checksums[5].getSplitName(), "config.hdpi");
+            assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[5].getInstallerPackageName());
+            assertNull(checksums[5].getInstallerCertificate());
+        }
+
+        // Update the package with one split+checksums and another split without checksums.
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
+            params.setAppPackageName(V4_PACKAGE_NAME);
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+
+            writeFileToSession(session, "hw5_split1", TEST_V4_SPLIT1);
+            session.addChecksums("hw5_split1", Arrays.asList(digests_split1));
+
+            writeFileToSession(session, "hw5_split2", TEST_V4_SPLIT2);
+
+            CommitIntentReceiver receiver = new CommitIntentReceiver();
+            session.commit(receiver.getIntentSender());
+            CommitIntentReceiver.checkSuccess(receiver.getResult());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+
+        {
+            LocalIntentReceiver receiver = new LocalIntentReceiver();
+            PackageManager pm = getPackageManager();
+            pm.requestChecksums(V4_PACKAGE_NAME, true, 0, TRUST_ALL, receiver.getIntentSender());
+            ApkChecksum[] checksums = receiver.getResult();
+            assertNotNull(checksums);
+            assertEquals(checksums.length, 10);
+            // base
+            assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[0].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[0].getValue()),
+                    "dd93e23bb8cdab0382fdca0d21a4f1cb");
+            assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertNotNull(checksums[0].getInstallerCertificate());
+            assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[1].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[1].getValue()),
+                    "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e");
+            assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertNotNull(checksums[1].getInstallerCertificate());
+            assertEquals(checksums[2].getSplitName(), null);
+            assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[2].getInstallerPackageName());
+            assertNull(checksums[2].getInstallerCertificate());
+            // split0
+            assertEquals(checksums[3].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[3].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[3].getValue()),
+                    "f6430e1b795ce2658c49e68d15316b2d");
+            assertEquals(checksums[3].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertNotNull(checksums[3].getInstallerCertificate());
+            assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[4].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[4].getValue()),
+                    "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e");
+            assertEquals(checksums[4].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertNotNull(checksums[4].getInstallerCertificate());
+            assertEquals(checksums[5].getSplitName(), "config.hdpi");
+            assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[5].getInstallerPackageName());
+            assertNull(checksums[5].getInstallerCertificate());
+            // split1
+            assertEquals(checksums[6].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[6].getSplitName(), "config.mdpi");
+            assertEquals(bytesToHexString(checksums[6].getValue()),
+                    "d1f4b00d034994663e84f907fe4bb664");
+            assertEquals(checksums[6].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertNotNull(checksums[6].getInstallerCertificate());
+            assertEquals(checksums[7].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[7].getSplitName(), "config.mdpi");
+            assertEquals(bytesToHexString(checksums[7].getValue()),
+                    "f16898f43990c14585a900eda345c3a236c6224f63920d69cfe8a7afbc0c0ccf");
+            assertEquals(checksums[7].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertNotNull(checksums[7].getInstallerCertificate());
+            assertEquals(checksums[8].getSplitName(), "config.mdpi");
+            assertEquals(checksums[8].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[8].getInstallerPackageName());
+            assertNull(checksums[8].getInstallerCertificate());
+            // split2
+            assertEquals(checksums[9].getSplitName(), "config.xhdpi");
+            assertEquals(checksums[9].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[9].getInstallerPackageName());
+            assertNull(checksums[9].getInstallerCertificate());
+        }
+    }
+
+    @Test
+    public void testInstallerChecksumsIncremental() throws Exception {
+        if (!checkIncrementalDeliveryFeature()) {
+            return;
+        }
+
+        installPackageIncrementally(TEST_FIXED_APK);
+
+        PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME, 0);
+        final String inPath = packageInfo.applicationInfo.getBaseCodePath();
+
+        installApkWithChecksumsIncrementally(inPath);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_ALL,
+                receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 3);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertNotNull(checksums[0].getInstallerCertificate());
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertNotNull(checksums[1].getInstallerCertificate());
+        assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[2].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[2].getInstallerPackageName());
+        assertNull(checksums[2].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerChecksumsIncrementalTrustNone() throws Exception {
+        if (!checkIncrementalDeliveryFeature()) {
+            return;
+        }
+
+        installPackageIncrementally(TEST_FIXED_APK);
+
+        PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME, 0);
+        final String inPath = packageInfo.applicationInfo.getBaseCodePath();
+
+        installApkWithChecksumsIncrementally(inPath);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalIntentReceiver receiver = new LocalIntentReceiver();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE,
+                receiver.getIntentSender());
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[0].getInstallerPackageName());
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    private List<Certificate> convertSignaturesToCertificates(Signature[] signatures) {
+        try {
+            final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            ArrayList<Certificate> certs = new ArrayList<>(signatures.length);
+            for (Signature signature : signatures) {
+                final InputStream is = new ByteArrayInputStream(signature.toByteArray());
+                final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
+                certs.add(cert);
+            }
+            return certs;
+        } catch (CertificateException e) {
+            throw ExceptionUtils.propagate(e);
+        }
+    }
+
+    private void installApkWithChecksums(Checksum[] checksums) throws Exception {
+        installApkWithChecksums("file", "file", checksums);
+    }
+
+    private void installApkWithChecksums(String apkName, String checksumsName, Checksum[] checksums)
+            throws Exception {
+        CommitIntentReceiver.checkSuccess(
+                installApkWithChecksums(TEST_FIXED_APK, apkName, checksumsName, checksums));
+    }
+
+    private Intent installApkWithChecksums(String apk, String apkName,
+            String checksumsName, Checksum[] checksums) throws Exception {
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+            writeFileToSession(session, apkName, apk);
+            session.addChecksums(checksumsName, Arrays.asList(checksums));
+
+            CommitIntentReceiver receiver = new CommitIntentReceiver();
+            session.commit(receiver.getIntentSender());
+            return receiver.getResult();
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    private void installApkWithChecksumsIncrementally(final String inPath) throws Exception {
+        final String apk = TEST_FIXED_APK;
+        final Checksum[] checksums = TEST_FIXED_APK_DIGESTS;
+
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+            params.setDataLoaderParams(DataLoaderParams.forIncremental(new ComponentName("android",
+                    PackageManagerShellCommandDataLoader.class.getName()), ""));
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+
+            final File file = new File(inPath);
+            final String name = file.getName();
+            final long size = file.length();
+            final Metadata metadata = Metadata.forLocalFile(inPath);
+
+            session.addFile(LOCATION_DATA_APP, name, size, metadata.toByteArray(), null);
+            session.addChecksums(name, Arrays.asList(checksums));
+
+            CommitIntentReceiver receiver = new CommitIntentReceiver();
+            session.commit(receiver.getIntentSender());
+            CommitIntentReceiver.checkSuccess(receiver.getResult());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    private static String readFullStream(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream result = new ByteArrayOutputStream();
+        writeFullStream(inputStream, result, -1);
+        return result.toString("UTF-8");
+    }
+
+    private static void writeFullStream(InputStream inputStream, OutputStream outputStream,
+            long expected)
+            throws IOException {
+        byte[] buffer = new byte[1024];
+        long total = 0;
+        int length;
+        while ((length = inputStream.read(buffer)) != -1) {
+            outputStream.write(buffer, 0, length);
+            total += length;
+        }
+        if (expected > 0) {
+            Assert.assertEquals(expected, total);
+        }
+    }
+
+    private static String executeShellCommand(String command) throws IOException {
+        final ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command);
+        try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
+            return readFullStream(inputStream);
+        }
+    }
+
+    private static String createApkPath(String baseName) {
+        return TEST_APK_PATH + baseName;
+    }
+
+    private void installPackage(String baseName) throws IOException {
+        File file = new File(createApkPath(baseName));
+        Assert.assertEquals("Success\n", executeShellCommand(
+                "pm install -t -g " + file.getPath()));
+    }
+
+    private void installPackageIncrementally(String baseName) throws IOException {
+        File file = new File(createApkPath(baseName));
+        Assert.assertEquals("Success\n", executeShellCommand(
+                "pm install-incremental -t -g " + file.getPath()));
+    }
+
+    private void installSplits(String[] baseNames) throws IOException {
+        String[] splits = Arrays.stream(baseNames).map(
+                baseName -> createApkPath(baseName)).toArray(String[]::new);
+        Assert.assertEquals("Success\n",
+                executeShellCommand("pm install -t -g " + String.join(" ", splits)));
+    }
+
+    private void installSplitsIncrementally(String[] baseNames) throws IOException {
+        String[] splits = Arrays.stream(baseNames).map(
+                baseName -> createApkPath(baseName)).toArray(String[]::new);
+        Assert.assertEquals("Success\n",
+                executeShellCommand("pm install-incremental -t -g " + String.join(" ", splits)));
+    }
+
+    private static void writeFileToSession(PackageInstaller.Session session, String name,
+            String apk) throws IOException {
+        File file = new File(createApkPath(apk));
+        try (OutputStream os = session.openWrite(name, 0, file.length());
+             InputStream is = new FileInputStream(file)) {
+            writeFullStream(is, os, file.length());
+        }
+    }
+
+    private String uninstallPackageSilently(String packageName) throws IOException {
+        return executeShellCommand("pm uninstall " + packageName);
+    }
+
+    private boolean isAppInstalled(String packageName) throws IOException {
+        final String commandResult = executeShellCommand("pm list packages");
+        final int prefixLength = "package:".length();
+        return Arrays.stream(commandResult.split("\\r?\\n"))
+                .anyMatch(line -> line.substring(prefixLength).equals(packageName));
+    }
+
+    @Nonnull
+    private static String bytesToHexString(byte[] bytes) {
+        return HexDump.toHexString(bytes, 0, bytes.length, /*upperCase=*/ false);
+    }
+
+    @Nonnull
+    private static byte[] hexStringToBytes(String hexString) {
+        return HexDump.hexStringToByteArray(hexString);
+    }
+
+    private boolean checkIncrementalDeliveryFeature() {
+        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY);
+    }
+
+    private static class LocalIntentReceiver {
+        private final LinkedBlockingQueue<ApkChecksum[]> mResult = new LinkedBlockingQueue<>();
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public void send(int code, Intent intent, String resolvedType, IBinder allowlistToken,
+                    IIntentReceiver finishedReceiver, String requiredPermission,
+                    Bundle options) {
+                Parcelable[] parcelables = intent.getParcelableArrayExtra(EXTRA_CHECKSUMS);
+                assertNotNull(parcelables);
+                ApkChecksum[] checksums = Arrays.copyOf(parcelables, parcelables.length,
+                        ApkChecksum[].class);
+                Arrays.sort(checksums, (ApkChecksum lhs, ApkChecksum rhs) ->  {
+                    final String lhsSplit = lhs.getSplitName();
+                    final String rhsSplit = rhs.getSplitName();
+                    if (Objects.equals(lhsSplit, rhsSplit)) {
+                        return Integer.signum(lhs.getType() - rhs.getType());
+                    }
+                    if (lhsSplit == null) {
+                        return -1;
+                    }
+                    if (rhsSplit == null) {
+                        return +1;
+                    }
+                    return lhsSplit.compareTo(rhsSplit);
+                });
+                mResult.offer(checksums);
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+
+        public ApkChecksum[] getResult() {
+            try {
+                return mResult.poll(5, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private static class CommitIntentReceiver {
+        private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public void send(int code, Intent intent, String resolvedType, IBinder allowlistToken,
+                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+                try {
+                    mResult.offer(intent, 5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+
+        public Intent getResult() {
+            try {
+                return mResult.take();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public static void checkSuccess(Intent result) {
+            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_FAILURE);
+            assertEquals(status, PackageInstaller.STATUS_SUCCESS,
+                    result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + " OR "
+                            + result.getExtras().get(Intent.EXTRA_INTENT));
+        }
+
+        public static void checkFailure(Intent result, String expectedStatus) {
+            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_FAILURE);
+            assertEquals(status, PackageInstaller.STATUS_FAILURE);
+            assertEquals(result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE),
+                    expectedStatus);
+        }
+    }
+}
diff --git a/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java b/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java
index b867f00e..46df40e 100644
--- a/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java
@@ -35,6 +35,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.platform.test.annotations.AppModeFull;
@@ -42,8 +43,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
-import org.junit.After;
-import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -59,6 +59,9 @@
     private static final String LOG_TAG = InstallSessionParamsUnitTest.class.getSimpleName();
     private static Optional UNSET = new Optional(false, null);
 
+    @Rule
+    public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
+
     @Parameterized.Parameter(0)
     public Optional<Integer> mode;
     @Parameterized.Parameter(1)
@@ -87,8 +90,6 @@
             .getPackageManager()
             .getPackageInstaller();
 
-    private int mSessionId = -1;
-
     /**
      * Generate test-parameters where all params are the same, but one param cycles through all
      * values.
@@ -200,21 +201,6 @@
         return null;
     }
 
-    @Before
-    public void resetSessionId() {
-        mSessionId = 1;
-    }
-
-    @After
-    public void abandonSession() {
-        if (mSessionId != -1) {
-            try {
-                mInstaller.abandonSession(mSessionId);
-            } catch (SecurityException ignored) {
-            }
-        }
-    }
-
     @Test
     public void checkSessionParams() throws Exception {
         Log.i(LOG_TAG, "mode=" + mode + " installLocation=" + installLocation + " size=" + size
@@ -234,8 +220,9 @@
         referredUri.ifPresent(params::setReferrerUri);
         installReason.ifPresent(params::setInstallReason);
 
+        int sessionId;
         try {
-            mSessionId = mInstaller.createSession(params);
+            sessionId = mInstaller.createSession(params);
 
             if (expectFailure) {
                 fail("Creating session did not fail");
@@ -248,7 +235,7 @@
             throw e;
         }
 
-        SessionInfo info = getSessionInfo(mSessionId);
+        SessionInfo info = getSessionInfo(sessionId);
 
         assertThat(info.getMode()).isEqualTo(mode.get());
         installLocation.ifPresent(i -> assertThat(info.getInstallLocation()).isEqualTo(i));
diff --git a/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java b/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
index 51752bc..a8f96af 100644
--- a/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
@@ -32,16 +32,16 @@
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
 import android.net.Uri;
 import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.util.FunctionalUtils;
-
 import libcore.io.Streams;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -55,6 +55,10 @@
 @RunWith(AndroidJUnit4.class)
 @AppModeFull // TODO(Instant) Figure out which APIs should work.
 public class InstallSessionTransferTest {
+
+    @Rule
+    public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
+
     /**
      * Get the sessionInfo if this package owns the session.
      *
@@ -111,6 +115,21 @@
         }
     }
 
+    /**
+     * Create a new installer session.
+     *
+     * @return The new session
+     */
+    private Session createSession() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        PackageInstaller installer = context.getPackageManager().getPackageInstaller();
+
+        SessionParams params = new SessionParams(MODE_FULL_INSTALL);
+        int sessionId = installer.createSession(params);
+        return installer.openSession(sessionId);
+    }
+
     @Test
     public void transferSession() throws Exception {
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -120,152 +139,140 @@
 
         PackageInstaller installer = context.getPackageManager().getPackageInstaller();
 
-        openAndCloseSession((sessionId, session) -> {
-            writeApk(session, "CtsContentTestCases");
+        SessionParams params = new SessionParams(MODE_FULL_INSTALL);
+        int sessionId = installer.createSession(params);
+        Session session = installer.openSession(sessionId);
 
-            InputStream danglingReadStream = session.openRead("CtsContentTestCases");
+        writeApk(session, "CtsContentTestCases");
 
-            SessionInfo info = getSessionInfo(installer, sessionId);
-            assertThat(info.getInstallerPackageName()).isEqualTo(context.getPackageName());
-            assertThat(info.isSealed()).isFalse();
+        InputStream danglingReadStream = session.openRead("CtsContentTestCases");
 
-            // This transfers the session to the new owner
-            session.transfer(packageInstallerPackage);
-            assertThat(getSessionInfo(installer, sessionId)).isNull();
+        SessionInfo info = getSessionInfo(installer, sessionId);
+        assertThat(info.getInstallerPackageName()).isEqualTo(context.getPackageName());
+        assertThat(info.isSealed()).isFalse();
 
-            try {
-                // Session is transferred, all operations on the session are invalid
-                session.getNames();
-                fail();
-            } catch (SecurityException e) {
-                // expected
-            }
+        // This transfers the session to the new owner
+        session.transfer(packageInstallerPackage);
+        assertThat(getSessionInfo(installer, sessionId)).isNull();
 
-            // Even when the session is transferred read streams still work and contain the same
-            // content that we initially wrote into it.
-            try (InputStream originalContent = new FileInputStream(
-                    "/data/local/tmp/cts/content/CtsContentTestCases.apk")) {
-                try (InputStream sessionContent = danglingReadStream) {
-                    byte[] buffer = new byte[4096];
-                    while (true) {
-                        int numReadOriginal = originalContent.read(buffer);
-                        int numReadSession = sessionContent.read(buffer);
+        try {
+            // Session is transferred, all operations on the session are invalid
+            session.getNames();
+            fail();
+        } catch (SecurityException e) {
+            // expected
+        }
 
-                        assertThat(numReadOriginal).isEqualTo(numReadSession);
-                        if (numReadOriginal == -1) {
-                            break;
-                        }
+        // Even when the session is transferred read streams still work and contain the same content
+        // that we initially wrote into it.
+        try (InputStream originalContent = new FileInputStream(
+                "/data/local/tmp/cts/content/CtsContentTestCases.apk")) {
+            try (InputStream sessionContent = danglingReadStream) {
+                byte[] buffer = new byte[4096];
+                while (true) {
+                    int numReadOriginal = originalContent.read(buffer);
+                    int numReadSession = sessionContent.read(buffer);
+
+                    assertThat(numReadOriginal).isEqualTo(numReadSession);
+                    if (numReadOriginal == -1) {
+                        break;
                     }
                 }
             }
+        }
 
-            danglingReadStream.close();
-        });
+        danglingReadStream.close();
     }
 
     @Test
     public void transferToInvalidNewOwner() throws Exception {
-        openAndCloseSession((sessionId, session) -> {
-            writeApk(session, "CtsContentTestCases");
+        Session session = createSession();
+        writeApk(session, "CtsContentTestCases");
 
-            try {
-                // This will fail as the name of the new owner is invalid
-                session.transfer("android.content.cts.invalid.package");
-                fail();
-            } catch (PackageManager.NameNotFoundException e) {
-                // Expected
-            }
-        });
+        try {
+            // This will fail as the name of the new owner is invalid
+            session.transfer("android.content.cts.invalid.package");
+            fail();
+        } catch (PackageManager.NameNotFoundException e) {
+            // Expected
+        }
+
+        session.abandon();
     }
 
     @Test
     public void transferToOwnerWithoutInstallPermission() throws Exception {
-        openAndCloseSession((sessionId, session) -> {
-            writeApk(session, "CtsContentTestCases");
+        Session session = createSession();
+        writeApk(session, "CtsContentTestCases");
 
-            try {
-                // This will fail as the current package does not own the install-packages
-                // permission
-                session.transfer(InstrumentationRegistry.getInstrumentation().getTargetContext()
-                        .getPackageName());
-                fail();
-            } catch (SecurityException e) {
-                // Expected
-            }
-        });
+        try {
+            // This will fail as the current package does not own the install-packages permission
+            session.transfer(InstrumentationRegistry.getInstrumentation().getTargetContext()
+                    .getPackageName());
+            fail();
+        } catch (SecurityException e) {
+            // Expected
+        }
+
+        session.abandon();
     }
 
     @Test
     public void transferWithOpenWrite() throws Exception {
-        openAndCloseSession((sessionId, session) -> {
-            String packageInstallerPackage = getPackageInstallerPackageName();
-            assumeNotNull(packageInstallerPackage);
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
 
-            session.openWrite("danglingWriteStream", 0, 1);
-            try {
-                // This will fail as the danglingWriteStream is still open
-                session.transfer(packageInstallerPackage);
-                fail();
-            } catch (SecurityException e) {
-                // Expected
-            }
-        });
+        session.openWrite("danglingWriteStream", 0, 1);
+        try {
+            // This will fail as the danglingWriteStream is still open
+            session.transfer(packageInstallerPackage);
+            fail();
+        } catch (SecurityException e) {
+            // Expected
+        }
+
+        session.abandon();
     }
 
     @Test
     public void transferSessionWithInvalidApk() throws Exception {
-        openAndCloseSession((sessionId, session) -> {
-            String packageInstallerPackage = getPackageInstallerPackageName();
-            assumeNotNull(packageInstallerPackage);
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
 
-            try (OutputStream out = session.openWrite("invalid", 0, 2)) {
-                out.write(new byte[]{23, 42});
-                out.flush();
-            }
+        try (OutputStream out = session.openWrite("invalid", 0, 2)) {
+            out.write(new byte[]{23, 42});
+            out.flush();
+        }
 
-            try {
-                // This will fail as the content of 'invalid' is not a valid APK
-                session.transfer(packageInstallerPackage);
-                fail();
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-        });
+        try {
+            // This will fail as the content of 'invalid' is not a valid APK
+            session.transfer(packageInstallerPackage);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        session.abandon();
     }
 
     @Test
     public void transferWithApkFromWrongPackage() throws Exception {
-        openAndCloseSession((sessionId, session) -> {
-            String packageInstallerPackage = getPackageInstallerPackageName();
-            assumeNotNull(packageInstallerPackage);
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
 
-            writeApk(session, "CtsContentEmptyTestApp");
+        writeApk(session, "CtsContentEmptyTestApp");
 
-            try {
-                // This will fail as the session contains the a apk from the wrong package
-                session.transfer(packageInstallerPackage);
-                fail();
-            } catch (SecurityException e) {
-                // expected
-            }
-        });
-    }
-
-    private void openAndCloseSession(FunctionalUtils.ThrowingBiConsumer<Integer, Session> block)
-            throws IOException {
-        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-
-        PackageInstaller installer = context.getPackageManager().getPackageInstaller();
-
-        SessionParams params = new SessionParams(MODE_FULL_INSTALL);
-        int sessionId = installer.createSession(params);
         try {
-            block.accept(sessionId, installer.openSession(sessionId));
-        } finally {
-            try {
-                installer.abandonSession(sessionId);
-            } catch (SecurityException ignored) {
-            }
+            // This will fail as the session contains the a apk from the wrong package
+            session.transfer(packageInstallerPackage);
+            fail();
+        } catch (SecurityException e) {
+            // expected
         }
+
+        session.abandon();
     }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
index 75f7cc4..4c5ef9a 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
@@ -63,6 +63,8 @@
 
     private static final String TEST_APK_PATH = "/data/local/tmp/cts/content/";
     private static final String TEST_APK = "HelloWorld5.apk";
+    private static final String TEST_APK_PROFILEABLE = "HelloWorld5Profileable.apk";
+    private static final String TEST_APK_SHELL = "HelloWorldShell.apk";
     private static final String TEST_APK_SPLIT = "HelloWorld5_hdpi-v4.apk";
 
     private static UiAutomation getUiAutomation() {
@@ -164,6 +166,16 @@
     }
 
     @Test
+    public void testSystemInstallWithIdSig() throws Exception {
+        final String baseName = TEST_APK_SHELL;
+        final File file = new File(createApkPath(baseName));
+        assertEquals(
+                "Failure [INSTALL_FAILED_SESSION_INVALID: Incremental installation of this "
+                        + "package is not allowed.]\n",
+                executeShellCommand("pm install-incremental -t -g " + file.getPath()));
+    }
+
+    @Test
     public void testInstallWithIdSigAndSplit() throws Exception {
         File apkfile = new File(createApkPath(TEST_APK));
         File splitfile = new File(createApkPath(TEST_APK_SPLIT));
@@ -233,7 +245,17 @@
 
     @LargeTest
     @Test
-    public void testInstallSysTrace() throws Exception {
+    public void testInstallSysTraceDebuggable() throws Exception {
+        doTestInstallSysTrace(TEST_APK);
+    }
+
+    @LargeTest
+    @Test
+    public void testInstallSysTraceProfileable() throws Exception {
+        doTestInstallSysTrace(TEST_APK_PROFILEABLE);
+    }
+
+    private void doTestInstallSysTrace(String testApk) throws Exception {
         // Async atrace dump uses less resources but requires periodic pulls.
         // Overall timeout of 30secs in 100ms intervals should be enough.
         final int atraceDumpIterations = 300;
@@ -250,7 +272,7 @@
                                 "atrace --async_dump");
                         try (InputStream inputStream =
                                      new ParcelFileDescriptor.AutoCloseInputStream(
-                                stdout)) {
+                                             stdout)) {
                             final String found = waitForSubstring(inputStream, expected);
                             if (!TextUtils.isEmpty(found)) {
                                 result.write(found.getBytes());
@@ -269,7 +291,7 @@
         readFromProcess.start();
 
         for (int i = 0; i < 3; ++i) {
-            installPackage(TEST_APK);
+            installPackage(testApk);
             assertTrue(isAppInstalled(TEST_APP_PACKAGE));
             uninstallPackageSilently(TEST_APP_PACKAGE);
         }
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
index d401e62..b3e0fff 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -27,17 +27,20 @@
 
 import android.app.UiAutomation;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageManager;
+import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
 import android.os.ParcelFileDescriptor;
-import android.os.incremental.IncrementalManager;
 import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.InstrumentationRegistry;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -51,9 +54,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
 import java.util.Optional;
 import java.util.Random;
 import java.util.stream.Collectors;
@@ -77,6 +78,9 @@
     private static final String TEST_HW7_SPLIT3 = "HelloWorld7_xxhdpi-v4.apk";
     private static final String TEST_HW7_SPLIT4 = "HelloWorld7_xxxhdpi-v4.apk";
 
+    @Rule
+    public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
+
     @Parameter
     public int mDataLoaderType;
 
@@ -89,7 +93,6 @@
     private boolean mStreaming = false;
     private boolean mIncremental = false;
     private String mInstall = "";
-    private List<Integer> mSessionIds = new ArrayList<>();
 
     private static PackageInstaller getPackageInstaller() {
         return InstrumentationRegistry.getContext().getPackageManager().getPackageInstaller();
@@ -153,7 +156,7 @@
     @Before
     public void onBefore() throws Exception {
         // Check if Incremental is allowed and revert to non-dataloader installation.
-        if (mDataLoaderType == DATA_LOADER_TYPE_INCREMENTAL && !IncrementalManager.isAllowed()) {
+        if (mDataLoaderType == DATA_LOADER_TYPE_INCREMENTAL && !checkIncrementalDeliveryFeature()) {
             mDataLoaderType = DATA_LOADER_TYPE_NONE;
         }
 
@@ -165,8 +168,6 @@
 
         uninstallPackageSilently(TEST_APP_PACKAGE);
         assertFalse(isAppInstalled(TEST_APP_PACKAGE));
-
-        mSessionIds.clear();
     }
 
     @After
@@ -174,13 +175,12 @@
         uninstallPackageSilently(TEST_APP_PACKAGE);
         assertFalse(isAppInstalled(TEST_APP_PACKAGE));
         assertEquals(null, getSplits(TEST_APP_PACKAGE));
+    }
 
-        for (int sessionId : mSessionIds) {
-            try {
-                getPackageInstaller().abandonSession(sessionId);
-            } catch (SecurityException ignored) {
-            }
-        }
+    private boolean checkIncrementalDeliveryFeature() {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        return context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_INCREMENTAL_DELIVERY);
     }
 
     @Test
@@ -197,8 +197,7 @@
         File file = new File(createApkPath(TEST_HW5));
         String command = "pm " + mInstall + " -t -g " + file.getPath() + (new Random()).nextLong();
         String commandResult = executeShellCommand(command);
-        assertEquals("Failure [INSTALL_FAILED_MEDIA_UNAVAILABLE: Failed to prepare image.]\n",
-                commandResult);
+        assertEquals("Failure [failed to add file(s)]\n", commandResult);
         assertFalse(isAppInstalled(TEST_APP_PACKAGE));
     }
 
@@ -423,7 +422,7 @@
 
             final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
 
-            final int sessionId = createSession(params);
+            final int sessionId = installer.createSession(params);
             PackageInstaller.Session session = installer.openSession(sessionId);
 
             assertEquals(null, session.getDataLoaderParams());
@@ -451,7 +450,7 @@
                     mIncremental ? DataLoaderParams.forIncremental(componentName, args)
                             : DataLoaderParams.forStreaming(componentName, args));
 
-            final int sessionId = createSession(params);
+            final int sessionId = installer.createSession(params);
             PackageInstaller.Session session = installer.openSession(sessionId);
 
             DataLoaderParams dataLoaderParams = session.getDataLoaderParams();
@@ -485,7 +484,7 @@
                     mIncremental ? DataLoaderParams.forIncremental(componentName, args)
                             : DataLoaderParams.forStreaming(componentName, args));
 
-            final int sessionId = createSession(params);
+            final int sessionId = installer.createSession(params);
             PackageInstaller.Session session = installer.openSession(sessionId);
 
             session.addFile(LOCATION_DATA_APP, "base.apk", 123, "123".getBytes(), null);
@@ -505,12 +504,6 @@
         }
     }
 
-    private int createSession(SessionParams params) throws IOException {
-        int sessionId = getPackageInstaller().createSession(params);
-        mSessionIds.add(sessionId);
-        return sessionId;
-    }
-
     private String createUpdateSession(String packageName) throws IOException {
         return createSession("-p " + packageName);
     }
@@ -521,10 +514,7 @@
         final String commandResult = executeShellCommand("pm install-create " + arg);
         assertTrue(commandResult, commandResult.startsWith(prefix));
         assertTrue(commandResult, commandResult.endsWith(suffix));
-        String sessionId = commandResult.substring(prefix.length(),
-                commandResult.length() - suffix.length());
-        mSessionIds.add(Integer.parseInt(sessionId));
-        return sessionId;
+        return commandResult.substring(prefix.length(), commandResult.length() - suffix.length());
     }
 
     private void addSplits(String sessionId, String[] splitNames) throws IOException {
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index b3785d8..db732cc 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -25,6 +25,13 @@
 import static android.content.pm.PackageManager.GET_PROVIDERS;
 import static android.content.pm.PackageManager.GET_RECEIVERS;
 import static android.content.pm.PackageManager.GET_SERVICES;
+import static android.content.pm.PackageManager.MATCH_APEX;
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
+import static android.content.pm.PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_INSTANT;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -36,6 +43,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
 
 import android.content.ComponentName;
 import android.content.Intent;
@@ -69,8 +77,11 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 /**
@@ -108,6 +119,10 @@
 
     private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
 
+    private static final int[] PACKAGE_INFO_MATCH_FLAGS = {MATCH_UNINSTALLED_PACKAGES,
+            MATCH_DISABLED_COMPONENTS, MATCH_SYSTEM_ONLY, MATCH_FACTORY_ONLY, MATCH_INSTANT,
+            MATCH_APEX, MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS};
+
     @Before
     public void setup() throws Exception {
         mPackageManager = InstrumentationRegistry.getContext().getPackageManager();
@@ -314,6 +329,14 @@
         // Test isSafeMode. Because the test case will not run in safe mode, so
         // the return will be false.
         assertFalse(mPackageManager.isSafeMode());
+
+        // Test getTargetSdkVersion
+        int expectedTargetSdk = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0)
+                .targetSdkVersion;
+        assertEquals(expectedTargetSdk, mPackageManager.getTargetSdkVersion(PACKAGE_NAME));
+        assertThrows(PackageManager.NameNotFoundException.class,
+                () -> mPackageManager.getTargetSdkVersion(
+                        "android.content.cts.non_existent_package"));
     }
 
     private void checkPackagesNameForUid(String expectedName, String[] uid) {
@@ -669,7 +692,7 @@
 
         // Check required permissions
         List<String> requestedPermissions = Arrays.asList(pkgInfo.requestedPermissions);
-        assertThat(requestedPermissions).containsAllOf(
+        assertThat(requestedPermissions).containsAtLeast(
                 "android.permission.MANAGE_ACCOUNTS",
                 "android.permission.ACCESS_NETWORK_STATE",
                 "android.content.cts.permission.TEST_GRANTED");
@@ -1011,4 +1034,90 @@
         assertThat(packageInfo.signatures)
                 .asList().containsExactly((Object[]) pastSigningCertificates);
     }
+
+    /**
+     * Runs a test for all combinations of a set of flags
+     * @param flagValues Which flags to use
+     * @param test The test
+     */
+    public void runTestWithFlags(int[] flagValues, Consumer<Integer> test) {
+        for (int i = 0; i < (1 << flagValues.length); i++) {
+            int flags = 0;
+            for (int j = 0; j < flagValues.length; j++) {
+                if ((i & (1 << j)) != 0) {
+                    flags |= flagValues[j];
+                }
+            }
+            try {
+                test.accept(flags);
+            } catch (Throwable t) {
+                throw new AssertionError(
+                        "Test failed for flags 0x" + String.format("%08x", flags), t);
+            }
+        }
+    }
+
+    /**
+     * Test that the MATCH_FACTORY_ONLY flag doesn't add new package names in the result of
+     * getInstalledPackages.
+     */
+    @Test
+    public void testGetInstalledPackages_WithFactoryFlag_IsSubset() {
+        runTestWithFlags(PACKAGE_INFO_MATCH_FLAGS,
+                this::testGetInstalledPackages_WithFactoryFlag_IsSubset);
+    }
+    public void testGetInstalledPackages_WithFactoryFlag_IsSubset(int flags) {
+        List<PackageInfo> packageInfos = mPackageManager.getInstalledPackages(flags);
+        List<PackageInfo> packageInfos2 = mPackageManager.getInstalledPackages(
+                flags | MATCH_FACTORY_ONLY);
+        Set<String> supersetNames =
+                packageInfos.stream().map(pi -> pi.packageName).collect(Collectors.toSet());
+
+        for (PackageInfo pi : packageInfos2) {
+            if (!supersetNames.contains(pi.packageName)) {
+                throw new AssertionError(
+                        "The subset contains packages that the superset doesn't contain.");
+            }
+        }
+    }
+
+    /**
+     * Test that the MATCH_FACTORY_ONLY flag filters out all non-system packages in the result of
+     * getInstalledPackages.
+     */
+    @Test
+    public void testGetInstalledPackages_WithFactoryFlag_ImpliesSystem() {
+        runTestWithFlags(PACKAGE_INFO_MATCH_FLAGS,
+                this::testGetInstalledPackages_WithFactoryFlag_ImpliesSystem);
+    }
+    public void testGetInstalledPackages_WithFactoryFlag_ImpliesSystem(int flags) {
+        List<PackageInfo> packageInfos =
+                mPackageManager.getInstalledPackages(flags | MATCH_FACTORY_ONLY);
+        for (PackageInfo pi : packageInfos) {
+            if (!pi.applicationInfo.isSystemApp()) {
+                throw new AssertionError(pi.packageName + " is not a system app.");
+            }
+        }
+    }
+
+    /**
+     * Test that the MATCH_FACTORY_ONLY flag doesn't add the same package multiple times since there
+     * may be multiple versions of a system package on the device.
+     */
+    @Test
+    public void testGetInstalledPackages_WithFactoryFlag_ContainsNoDuplicates() {
+        runTestWithFlags(PACKAGE_INFO_MATCH_FLAGS,
+                this::testGetInstalledPackages_WithFactoryFlag_ContainsNoDuplicates);
+    }
+    public void testGetInstalledPackages_WithFactoryFlag_ContainsNoDuplicates(int flags) {
+        List<PackageInfo> packageInfos =
+                mPackageManager.getInstalledPackages(flags | MATCH_FACTORY_ONLY);
+        Set<String> foundPackages = new HashSet<>();
+        for (PackageInfo pi : packageInfos) {
+            if (foundPackages.contains(pi.packageName)) {
+                throw new AssertionError(pi.packageName + " is listed at least twice.");
+            }
+            foundPackages.add(pi.packageName);
+        }
+    }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/PermissionFeatureTest.java b/tests/tests/content/src/android/content/pm/cts/PermissionFeatureTest.java
index 7fc3af0..1107467 100644
--- a/tests/tests/content/src/android/content/pm/cts/PermissionFeatureTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PermissionFeatureTest.java
@@ -23,44 +23,62 @@
 @AppModeFull // TODO(Instant) Figure out which APIs should work.
 public class PermissionFeatureTest extends AndroidTestCase {
     public void testPermissionRequiredFeatureDefined() {
-        PackageManager pm = getContext().getPackageManager();
-        assertEquals(PackageManager.PERMISSION_GRANTED,
-                pm.checkPermission("android.content.cts.REQUIRED_FEATURE_DEFINED",
-                        getContext().getPackageName()));
+        assertPermissionGranted("android.content.cts.REQUIRED_FEATURE_DEFINED");
+    }
+
+    public void testPermissionRequiredFeatureDefined_usingTags() {
+        assertPermissionGranted("android.content.cts.REQUIRED_FEATURE_DEFINED_2");
     }
 
     public void testPermissionRequiredFeatureUndefined() {
-        PackageManager pm = getContext().getPackageManager();
-        assertEquals(PackageManager.PERMISSION_DENIED,
-                pm.checkPermission("android.content.cts.REQUIRED_FEATURE_UNDEFINED",
-                        getContext().getPackageName()));
+        assertPermissionDenied("android.content.cts.REQUIRED_FEATURE_UNDEFINED");
     }
 
     public void testPermissionRequiredNotFeatureDefined() {
-        PackageManager pm = getContext().getPackageManager();
-        assertEquals(PackageManager.PERMISSION_DENIED,
-                pm.checkPermission("android.content.cts.REQUIRED_NOT_FEATURE_DEFINED",
-                        getContext().getPackageName()));
+        assertPermissionDenied("android.content.cts.REQUIRED_NOT_FEATURE_DEFINED");
     }
 
     public void testPermissionRequiredNotFeatureUndefined() {
-        PackageManager pm = getContext().getPackageManager();
-        assertEquals(PackageManager.PERMISSION_GRANTED,
-                pm.checkPermission("android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED",
-                        getContext().getPackageName()));
+        assertPermissionGranted("android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED");
+    }
+
+    public void testPermissionRequiredNotFeatureUndefined_usingTags() {
+        assertPermissionGranted("android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED_2");
     }
 
     public void testPermissionRequiredMultiDeny() {
-        PackageManager pm = getContext().getPackageManager();
-        assertEquals(PackageManager.PERMISSION_DENIED,
-                pm.checkPermission("android.content.cts.REQUIRED_MULTI_DENY",
-                        getContext().getPackageName()));
+        assertPermissionDenied("android.content.cts.REQUIRED_MULTI_DENY");
+    }
+
+    public void testPermissionRequiredMultiDeny_usingTags() {
+        assertPermissionDenied("android.content.cts.REQUIRED_MULTI_DENY_2");
+    }
+
+    public void testPermissionRequiredMultiDeny_usingTagsAndAttributes() {
+        assertPermissionDenied("android.content.cts.REQUIRED_MULTI_DENY_3");
     }
 
     public void testPermissionRequiredMultiGrant() {
-        PackageManager pm = getContext().getPackageManager();
+        assertPermissionGranted("android.content.cts.REQUIRED_MULTI_GRANT");
+    }
+
+    public void testPermissionRequiredMultiGrant_usingTags() {
+        assertPermissionGranted("android.content.cts.REQUIRED_MULTI_GRANT_2");
+    }
+
+    public void testPermissionRequiredMultiGrant_usingTagsAndAttributes() {
+        assertPermissionGranted("android.content.cts.REQUIRED_MULTI_GRANT_3");
+    }
+
+    public void assertPermissionGranted(String permName) {
+        final PackageManager pm = getContext().getPackageManager();
         assertEquals(PackageManager.PERMISSION_GRANTED,
-                pm.checkPermission("android.content.cts.REQUIRED_MULTI_GRANT",
-                        getContext().getPackageName()));
+                pm.checkPermission(permName, getContext().getPackageName()));
+    }
+
+    public void assertPermissionDenied(String permName) {
+        final PackageManager pm = getContext().getPackageManager();
+        assertEquals(PackageManager.PERMISSION_DENIED,
+                pm.checkPermission(permName, getContext().getPackageName()));
     }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/util/AbandonAllPackageSessionsRule.kt b/tests/tests/content/src/android/content/pm/cts/util/AbandonAllPackageSessionsRule.kt
new file mode 100644
index 0000000..db446f2
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/util/AbandonAllPackageSessionsRule.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.cts.util
+
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Abandons all sessions for the instrumentation package after the test has finished.
+ */
+class AbandonAllPackageSessionsRule : TestRule {
+    override fun apply(base: Statement, description: Description?) = object : Statement() {
+        override fun evaluate() {
+            try {
+                base.evaluate()
+            } finally {
+                val packageInstaller = InstrumentationRegistry.getInstrumentation()
+                        .getContext().packageManager.packageInstaller
+                packageInstaller.mySessions.forEach {
+                    try {
+                        packageInstaller.abandonSession(it.sessionId)
+                    } catch (ignored: Exception) {
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/content/src/android/content/res/cts/AssetManagerTest.java b/tests/tests/content/src/android/content/res/cts/AssetManagerTest.java
index 6998103..e4ec383 100644
--- a/tests/tests/content/src/android/content/res/cts/AssetManagerTest.java
+++ b/tests/tests/content/src/android/content/res/cts/AssetManagerTest.java
@@ -102,7 +102,7 @@
 
         // We don't do an exact match because the framework can add asset files and this test
         // would be too brittle.
-        assertThat(files).asList().containsAllOf(fileName, "subdir");
+        assertThat(files).asList().containsAtLeast(fileName, "subdir");
 
         files = mAssets.list("subdir");
         assertThat(files).isNotNull();
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
index 4db1ee5..dbc760e 100644
--- a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
@@ -48,6 +48,7 @@
         mConfig.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
         mConfig.navigation = Configuration.NAVIGATION_NONAV;
         mConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
+        mConfig.forceBoldText = Configuration.FORCE_BOLD_TEXT_YES;
     }
 
     public void testConstructor() {
@@ -60,6 +61,13 @@
         final Configuration cfg2 = new Configuration();
         assertEquals(0, cfg1.compareTo(cfg2));
 
+        cfg1.forceBoldText = 1;
+        cfg2.forceBoldText = 2;
+        assertEquals(-1, cfg1.compareTo(cfg2));
+        cfg1.forceBoldText = 2;
+        cfg2.forceBoldText = 1;
+        assertEquals(1, cfg1.compareTo(cfg2));
+
         cfg1.colorMode = 2;
         cfg2.colorMode = 3;
         assertEquals(-1, cfg1.compareTo(cfg2));
@@ -183,7 +191,7 @@
         assertEquals(expectedFlags, tmpc1.updateFrom(c2));
         assertEquals(0, tmpc1.diff(c2));
     }
-    
+
     public void testDiff() {
         Configuration config = new Configuration();
         config.mcc = 1;
@@ -312,6 +320,21 @@
                 | ActivityInfo.CONFIG_UI_MODE
                 | ActivityInfo.CONFIG_FONT_SCALE
                 | ActivityInfo.CONFIG_COLOR_MODE, mConfigDefault, config);
+        config.forceBoldText = 2;
+        doConfigCompare(ActivityInfo.CONFIG_MCC
+                | ActivityInfo.CONFIG_MNC
+                | ActivityInfo.CONFIG_LOCALE
+                | ActivityInfo.CONFIG_LAYOUT_DIRECTION
+                | ActivityInfo.CONFIG_SCREEN_LAYOUT
+                | ActivityInfo.CONFIG_TOUCHSCREEN
+                | ActivityInfo.CONFIG_KEYBOARD
+                | ActivityInfo.CONFIG_KEYBOARD_HIDDEN
+                | ActivityInfo.CONFIG_NAVIGATION
+                | ActivityInfo.CONFIG_ORIENTATION
+                | ActivityInfo.CONFIG_UI_MODE
+                | ActivityInfo.CONFIG_FONT_SCALE
+                | ActivityInfo.CONFIG_COLOR_MODE
+                | ActivityInfo.CONFIG_FORCE_BOLD_TEXT, mConfigDefault, config);
     }
 
     public void testEquals() {
@@ -363,6 +386,7 @@
                 config.smallestScreenWidthDp);
         assertEquals(Configuration.DENSITY_DPI_UNDEFINED, config.densityDpi);
         assertEquals(Configuration.COLOR_MODE_UNDEFINED, config.colorMode);
+        assertEquals(Configuration.FORCE_BOLD_TEXT_UNDEFINED, config.forceBoldText);
     }
 
     public void testUnset() {
@@ -390,6 +414,7 @@
                 config.smallestScreenWidthDp);
         assertEquals(Configuration.DENSITY_DPI_UNDEFINED, config.densityDpi);
         assertEquals(Configuration.COLOR_MODE_UNDEFINED, config.colorMode);
+        assertEquals(Configuration.FORCE_BOLD_TEXT_UNDEFINED, config.forceBoldText);
     }
 
     public void testToString() {
diff --git a/tests/tests/cronet/TEST_MAPPING b/tests/tests/cronet/TEST_MAPPING
new file mode 100644
index 0000000..b1f3088
--- /dev/null
+++ b/tests/tests/cronet/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsCronetTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/database/TEST_MAPPING b/tests/tests/database/TEST_MAPPING
new file mode 100644
index 0000000..4a7fa66
--- /dev/null
+++ b/tests/tests/database/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsDatabaseTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
index 125197c..f4b293d 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
@@ -743,6 +743,18 @@
     }
 
     @Test
+    public void testStrictQueryEmptyToken() {
+        for (String column : COLUMNS_VALID) {
+            assertStrictQueryValid(
+                    new String[] { column }, column + "=\"\"", null, null, null, null, null);
+        }
+        for (String column : COLUMNS_INVALID) {
+            assertStrictQueryInvalid(
+                    new String[] { column }, column + "=\"\"", null, null, null, null, null);
+        }
+    }
+
+    @Test
     public void testStrictQueryOrderBy() {
         for (String column : COLUMNS_VALID) {
             assertStrictQueryValid(
diff --git a/tests/tests/deviceconfig/AndroidTest.xml b/tests/tests/deviceconfig/AndroidTest.xml
index 0d3a8b8..e869e1f 100644
--- a/tests/tests/deviceconfig/AndroidTest.xml
+++ b/tests/tests/deviceconfig/AndroidTest.xml
@@ -18,7 +18,7 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/AbstractDeviceConfigTestCase.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/AbstractDeviceConfigTestCase.java
deleted file mode 100644
index 462ee62..0000000
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/AbstractDeviceConfigTestCase.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.deviceconfig.cts;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.os.UserHandle;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.Executor;
-
-@RunWith(AndroidJUnit4.class)
-abstract class AbstractDeviceConfigTestCase {
-
-    static final Context CONTEXT = InstrumentationRegistry.getContext();
-    static final Executor EXECUTOR = CONTEXT.getMainExecutor();
-
-    static final String WRITE_DEVICE_CONFIG_PERMISSION = "android.permission.WRITE_DEVICE_CONFIG";
-    static final String READ_DEVICE_CONFIG_PERMISSION = "android.permission.READ_DEVICE_CONFIG";
-
-    // String used to skip tests if not support.
-    // TODO: ideally it would be simpler to just use assumeTrue() in the @BeforeClass method, but
-    // then the test would crash - it might be an issue on atest / AndroidJUnit4
-    private static String sUnsupportedReason;
-
-    /**
-     * Get necessary permissions to access and modify properties through DeviceConfig API.
-     */
-    @BeforeClass
-    public static void setUp() throws Exception {
-        if (CONTEXT.getUserId() != UserHandle.USER_SYSTEM
-                && CONTEXT.getPackageManager().isInstantApp()) {
-            sUnsupportedReason = "cannot run test as instant app on secondary user "
-                    + CONTEXT.getUserId();
-        }
-    }
-
-    @Before
-    public void assumeSupported() {
-        assumeTrue(sUnsupportedReason, isSupported());
-    }
-
-    static boolean isSupported() {
-        return sUnsupportedReason == null;
-    }
-}
\ No newline at end of file
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
index a18bbca..6d77ebb 100644
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
+++ b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
@@ -16,8 +16,6 @@
 
 package android.deviceconfig.cts;
 
-import androidx.test.InstrumentationRegistry;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
@@ -25,13 +23,17 @@
 import android.provider.DeviceConfig.OnPropertiesChangedListener;
 import android.provider.DeviceConfig.Properties;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.concurrent.Executor;
 
-public final class DeviceConfigApiPermissionTests extends AbstractDeviceConfigTestCase {
+@RunWith(AndroidJUnit4.class)
+public final class DeviceConfigApiPermissionTests {
     private static final String NAMESPACE = "namespace";
     private static final String NAMESPACE2 = "namespace2";
     private static final String PUBLIC_NAMESPACE = "textclassifier";
@@ -39,10 +41,16 @@
     private static final String KEY2 = "key2";
     private static final String VALUE = "value";
 
+    private static final String WRITE_DEVICE_CONFIG_PERMISSION =
+            "android.permission.WRITE_DEVICE_CONFIG";
+
+    private static final String READ_DEVICE_CONFIG_PERMISSION =
+            "android.permission.READ_DEVICE_CONFIG";
+
+    private static final Executor EXECUTOR = InstrumentationRegistry.getContext().getMainExecutor();
+
     @After
     public void dropShellPermissionIdentityAfterTest() {
-        if (!isSupported()) return;
-
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .dropShellPermissionIdentity();
     }
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
index 9fb039d..c1cbbad 100644
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
+++ b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
@@ -20,17 +20,22 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
 import static org.junit.Assert.fail;
 
+import android.content.Context;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.OnPropertiesChangedListener;
 import android.provider.DeviceConfig.Properties;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.AfterClass;
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,7 +45,8 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
-public final class DeviceConfigApiTests extends AbstractDeviceConfigTestCase {
+@RunWith(AndroidJUnit4.class)
+public final class DeviceConfigApiTests {
     private static final String NAMESPACE1 = "namespace1";
     private static final String NAMESPACE2 = "namespace2";
     private static final String EMPTY_NAMESPACE = "empty_namespace";
@@ -68,20 +74,47 @@
     private static final float VALID_FLOAT = 456.789f;
     private static final String INVALID_FLOAT = "34343et";
 
+    private static final Context CONTEXT = InstrumentationRegistry.getContext();
+
+    private static final Executor EXECUTOR = CONTEXT.getMainExecutor();
+
+
     private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
     private final Object mLock = new Object();
 
+
+    private static final String WRITE_DEVICE_CONFIG_PERMISSION =
+            "android.permission.WRITE_DEVICE_CONFIG";
+
+    private static final String READ_DEVICE_CONFIG_PERMISSION =
+            "android.permission.READ_DEVICE_CONFIG";
+
+    // String used to skip tests if not support.
+    // TODO: ideally it would be simpler to just use assumeTrue() in the @BeforeClass method, but
+    // then the test would crash - it might be an issue on atest / AndroidJUnit4
+    private static String sUnsupportedReason;
+
     /**
      * Get necessary permissions to access and modify properties through DeviceConfig API.
      */
     @BeforeClass
     public static void setUp() throws Exception {
-        if (!isSupported()) return;
+        if (CONTEXT.getUserId() != UserHandle.USER_SYSTEM
+                && CONTEXT.getPackageManager().isInstantApp()) {
+            sUnsupportedReason = "cannot run test as instant app on secondary user "
+                    + CONTEXT.getUserId();
+            return;
+        }
 
         InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                 WRITE_DEVICE_CONFIG_PERMISSION, READ_DEVICE_CONFIG_PERMISSION);
     }
 
+    @Before
+    public void assumeSupported() {
+        assumeTrue(sUnsupportedReason, isSupported());
+    }
+
     /**
      * Nullify properties in DeviceConfig API after completion of every test.
      */
@@ -1060,6 +1093,10 @@
                 "device_config delete " + namespace + " " + key);
     }
 
+    private static boolean isSupported() {
+        return sUnsupportedReason == null;
+    }
+
     private static class PropertyUpdate {
         Properties properties;
 
@@ -1090,4 +1127,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/tests/display/src/android/display/cts/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
index 639ecc8..03c8220 100644
--- a/tests/tests/display/src/android/display/cts/DisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.*;
 
+import android.Manifest;
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.Presentation;
@@ -40,6 +41,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.view.Display;
 import android.view.Display.HdrCapabilities;
 import android.view.View;
@@ -58,14 +60,21 @@
 
 import java.io.FileInputStream;
 import java.io.InputStream;
+import java.time.Duration;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.Random;
 import java.util.Scanner;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class DisplayTest {
+    private static final String TAG = "DisplayTest";
+
     // The CTS package brings up an overlay display on the target device (see AndroidTest.xml).
     // The overlay display parameters must match the ones defined there which are
     // 181x161/214 (wxh/dpi).  It only matters that these values are different from any real
@@ -111,6 +120,8 @@
         mUiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
         mDefaultDisplay = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
         mSupportedWideGamuts = mDefaultDisplay.getSupportedWideColorGamut();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS);
     }
 
     @After
@@ -118,6 +129,9 @@
         if (mScreenOnActivity != null) {
             mScreenOnActivity.finish();
         }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+
     }
 
     private void enableAppOps() {
@@ -200,8 +214,7 @@
      */
     @Test
     public void testDefaultDisplayHdrCapability() {
-        Display display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
-        HdrCapabilities cap = display.getHdrCapabilities();
+        HdrCapabilities cap = mDefaultDisplay.getHdrCapabilities();
         int[] hdrTypes = cap.getSupportedHdrTypes();
         for (int type : hdrTypes) {
             assertTrue(type >= 1 && type <= 4);
@@ -212,9 +225,9 @@
         assertTrue(cap.getDesiredMinLuminance() <= cap.getDesiredMaxAverageLuminance());
         assertTrue(cap.getDesiredMaxAverageLuminance() <= cap.getDesiredMaxLuminance());
         if (hdrTypes.length > 0) {
-            assertTrue(display.isHdr());
+            assertTrue(mDefaultDisplay.isHdr());
         } else {
-            assertFalse(display.isHdr());
+            assertFalse(mDefaultDisplay.isHdr());
         }
     }
 
@@ -306,6 +319,98 @@
     }
 
     /**
+     * Test that a mode switch to every reported display mode is successful.
+     */
+    @Test
+    public void testModeSwitchOnPrimaryDisplay() throws Exception {
+        Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+        if (modes.length == 1) {
+            // If there's only one mode we can't do mode switches.
+            return;
+        }
+
+        // Create a deterministically shuffled list of display modes, which ends with the
+        // current active mode. We'll switch to the modes in this order. The active mode is last
+        // so we don't need an extra mode switch in case the test completes successfully.
+        Display.Mode activeMode = mDefaultDisplay.getMode();
+        List<Display.Mode> modesList = new ArrayList<>(modes.length);
+        for (Display.Mode mode : modes) {
+            if (mode.getModeId() != activeMode.getModeId()) {
+                modesList.add(mode);
+            }
+        }
+        Random random = new Random(42);
+        Collections.shuffle(modesList, random);
+        modesList.add(activeMode);
+
+        try {
+            mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+            assertTrue(mDisplayManager.shouldAlwaysRespectAppRequestedMode());
+            final Activity activity = launchActivity(mDisplayTestActivity);
+            for (Display.Mode mode : modesList) {
+                testSwitchToModeId(activity, mode);
+            }
+        } finally {
+            mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+        }
+    }
+
+    private void testSwitchToModeId(Activity activity, Display.Mode mode) throws Exception {
+        Log.i(TAG, "Switching to mode " + mode);
+
+        final CountDownLatch changeSignal = new CountDownLatch(1);
+        final AtomicInteger changeCounter = new AtomicInteger(0);
+        final int activeModeId = mDefaultDisplay.getMode().getModeId();
+
+        DisplayListener listener = new DisplayListener() {
+            private int mLastModeId = activeModeId;
+            @Override
+            public void onDisplayAdded(int displayId) {}
+
+            @Override
+            public void onDisplayChanged(int displayId) {
+                if (displayId != mDefaultDisplay.getDisplayId()) {
+                    return;
+                }
+                int newModeId = mDefaultDisplay.getMode().getModeId();
+                if (mLastModeId == newModeId) {
+                    // We assume this display change is caused by an external factor so it's
+                    // unrelated.
+                    return;
+                }
+                changeCounter.incrementAndGet();
+                changeSignal.countDown();
+
+                mLastModeId = newModeId;
+            }
+
+            @Override
+            public void onDisplayRemoved(int displayId) {}
+        };
+
+        Handler handler = new Handler(Looper.getMainLooper());
+        mDisplayManager.registerDisplayListener(listener, handler);
+
+        final CountDownLatch presentationSignal = new CountDownLatch(1);
+        handler.post(() -> {
+            WindowManager.LayoutParams params = activity.getWindow().getAttributes();
+            params.preferredDisplayModeId = mode.getModeId();
+            activity.getWindow().setAttributes(params);
+            presentationSignal.countDown();
+        });
+
+        assertTrue(presentationSignal.await(5, TimeUnit.SECONDS));
+
+        // Wait until the display change is effective.
+        assertTrue(changeSignal.await(5, TimeUnit.SECONDS));
+        assertEquals(mode.getModeId(), mDefaultDisplay.getMode().getModeId());
+
+        // Make sure no more display mode changes are registered.
+        Thread.sleep(Duration.ofSeconds(3).toMillis());
+        assertEquals(1, changeCounter.get());
+    }
+
+    /**
      * Tests that the mode-related attributes and methods work as expected.
      */
     @Test
@@ -320,10 +425,10 @@
     }
 
     /**
-     * Tests that mode switch requests are correctly executed.
+     * Test that refresh rate switch app requests are correctly executed on a secondary display.
      */
     @Test
-    public void testModeSwitch() throws Exception {
+    public void testRefreshRateSwitchOnSecondaryDisplay() throws Exception {
         // Standalone VR devices globally ignore SYSTEM_ALERT_WINDOW via AppOps.
         // Skip this test, which depends on a Presentation SYSTEM_ALERT_WINDOW to pass.
         if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_VR_HEADSET) {
@@ -359,15 +464,12 @@
 
         // Show the presentation.
         final CountDownLatch presentationSignal = new CountDownLatch(1);
-        handler.post(new Runnable() {
-            @Override
-            public void run() {
-                mPresentation = new TestPresentation(
-                        InstrumentationRegistry.getInstrumentation().getContext(),
-                        display, newMode.getModeId());
-                mPresentation.show();
-                presentationSignal.countDown();
-            }
+        handler.post(() -> {
+            mPresentation = new TestPresentation(
+                    InstrumentationRegistry.getInstrumentation().getContext(),
+                    display, newMode.getModeId());
+            mPresentation.show();
+            presentationSignal.countDown();
         });
         assertTrue(presentationSignal.await(5, TimeUnit.SECONDS));
 
@@ -375,12 +477,7 @@
         assertTrue(changeSignal.await(5, TimeUnit.SECONDS));
 
         assertEquals(newMode, display.getMode());
-        handler.post(new Runnable() {
-            @Override
-            public void run() {
-                mPresentation.dismiss();
-            }
-        });
+        handler.post(() -> mPresentation.dismiss());
     }
 
     /**
diff --git a/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
index 3b2406f..ecae16f 100644
--- a/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
@@ -16,6 +16,9 @@
 
 package android.display.cts;
 
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+
 import android.app.Presentation;
 import android.content.Context;
 import android.graphics.Color;
@@ -75,9 +78,6 @@
     private HandlerThread mCheckThread;
     private Handler mCheckHandler;
 
-    private static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9;
-    private static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10;
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
diff --git a/tests/tests/dpi2/TEST_MAPPING b/tests/tests/dpi2/TEST_MAPPING
new file mode 100644
index 0000000..09c90d6
--- /dev/null
+++ b/tests/tests/dpi2/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsDpiTestCases2"
+    }
+  ]
+}
diff --git a/tests/tests/dreams/TEST_MAPPING b/tests/tests/dreams/TEST_MAPPING
new file mode 100644
index 0000000..d8c6848
--- /dev/null
+++ b/tests/tests/dreams/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsDreamsTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/drm/TEST_MAPPING b/tests/tests/drm/TEST_MAPPING
new file mode 100644
index 0000000..437c95c
--- /dev/null
+++ b/tests/tests/drm/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsDrmTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/effect/TEST_MAPPING b/tests/tests/effect/TEST_MAPPING
new file mode 100644
index 0000000..623dc8c
--- /dev/null
+++ b/tests/tests/effect/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsEffectTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/externalservice/TEST_MAPPING b/tests/tests/externalservice/TEST_MAPPING
new file mode 100644
index 0000000..044b14f
--- /dev/null
+++ b/tests/tests/externalservice/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsExternalServiceTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/gesture/TEST_MAPPING b/tests/tests/gesture/TEST_MAPPING
new file mode 100644
index 0000000..7572991
--- /dev/null
+++ b/tests/tests/gesture/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsGestureTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/graphics/AndroidManifest.xml b/tests/tests/graphics/AndroidManifest.xml
index 2e53417..6406133 100644
--- a/tests/tests/graphics/AndroidManifest.xml
+++ b/tests/tests/graphics/AndroidManifest.xml
@@ -16,69 +16,66 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.graphics.cts"
-        android:targetSandboxVersion="2">
+     package="android.graphics.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.graphics.cts.CameraGpuCtsActivity"
-            android:label="CameraGpuCtsActivity">
+             android:label="CameraGpuCtsActivity">
         </activity>
 
         <activity android:name="android.graphics.cts.FrameRateCtsActivity"
-            android:label="FrameRateCtsActivity">
+             android:label="FrameRateCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.graphics.cts.ImageViewCtsActivity"
-            android:label="ImageViewCtsActivity">
+             android:label="ImageViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.graphics.cts.VulkanPreTransformCtsActivity"
-            android:label="VulkanPreTransformCtsActivity"
-            android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+             android:label="VulkanPreTransformCtsActivity"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
         </activity>
 
         <activity android:name="android.graphics.drawable.cts.DrawableStubActivity"
-                  android:theme="@style/WhiteBackgroundNoWindowAnimation"
-          android:screenOrientation="locked"/>
+             android:theme="@style/WhiteBackgroundNoWindowAnimation"
+             android:screenOrientation="locked"/>
         <activity android:name="android.graphics.drawable.cts.AnimatedImageActivity"
-                  android:theme="@style/WhiteBackgroundNoWindowAnimation"
-            android:screenOrientation="locked">
+             android:theme="@style/WhiteBackgroundNoWindowAnimation"
+             android:screenOrientation="locked">
         </activity>
-        <provider
-            android:name=".EmptyProvider"
-            android:exported="true"
-            android:authorities="android.graphics.cts.assets"/>
-        <provider
-            android:name="androidx.core.content.FileProvider"
-            android:authorities="android.graphics.cts.fileprovider"
-            android:exported="false"
-            android:grantUriPermissions="true"
-            >
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/file_paths" />
+        <provider android:name=".EmptyProvider"
+             android:exported="true"
+             android:authorities="android.graphics.cts.assets"/>
+        <provider android:name="androidx.core.content.FileProvider"
+             android:authorities="android.graphics.cts.fileprovider"
+             android:exported="false"
+             android:grantUriPermissions="true">
+            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+                 android:resource="@xml/file_paths"/>
         </provider>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.graphics.cts"
-                     android:label="CTS tests of android.graphics">
+         android:targetPackage="android.graphics.cts"
+         android:label="CTS tests of android.graphics">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttf b/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttf
new file mode 100644
index 0000000..ef655363
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttx b/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttx
new file mode 100644
index 0000000..3502b37
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttx
@@ -0,0 +1,240 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="a"/>
+    <GlyphID id="2" name="b"/>
+    <GlyphID id="3" name="c"/>
+    <GlyphID id="4" name="d"/>
+    <GlyphID id="5" name="e"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="1.0"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="1000"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="a" width="1000" lsb="0"/>
+    <mtx name="b" width="1000" lsb="0"/>
+    <mtx name="c" width="1000" lsb="0"/>
+    <mtx name="d" width="1000" lsb="0"/>
+    <mtx name="e" width="1000" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 format="12" reserved="0" length="3" nGroups="6" platformID="3" platEncID="10" language="0">
+      <map code="0x0061" name="a" />
+      <map code="0x0062" name="b" />
+      <map code="0x0063" name="c" />
+      <map code="0x0064" name="d" />
+      <map code="0x0065" name="e" />
+    </cmap_format_12>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="a" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="500" y="1000" on="1" />
+        <pt x="1000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="b" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="250" on="1" />
+        <pt x="0" y="500" on="1" />
+        <pt x="1000" y="750" on="1" />
+        <pt x="0" y="1000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="c" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="0" on="1" />
+        <pt x="0" y="500" on="1" />
+        <pt x="1000" y="1000" on="1" />
+        <pt x="0" y="1000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="d" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="250" on="1" />
+        <pt x="1000" y="750" on="1" />
+        <pt x="0" y="1000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="e" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="0" on="1" />
+        <pt x="0" y="250" on="1" />
+        <pt x="1000" y="500" on="1" />
+        <pt x="0" y="750" on="1" />
+        <pt x="1000" y="1000" on="1" />
+        <pt x="0" y="1000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2017 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/measurement/a3em.ttf b/tests/tests/graphics/assets/fonts/measurement/a3em.ttf
new file mode 100644
index 0000000..e7814db
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/measurement/a3em.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/measurement/a3em.ttx b/tests/tests/graphics/assets/fonts/measurement/a3em.ttx
index d3b9e16..19f1712 100644
--- a/tests/tests/graphics/assets/fonts/measurement/a3em.ttx
+++ b/tests/tests/graphics/assets/fonts/measurement/a3em.ttx
@@ -101,7 +101,7 @@
     <fsSelection value="00000000 01000000"/>
     <usFirstCharIndex value="32"/>
     <usLastCharIndex value="122"/>
-    <sTypoAscender value="800"/>
+    <sTypoAscender value="1000"/>
     <sTypoDescender value="-200"/>
     <sTypoLineGap value="200"/>
     <usWinAscent value="1000"/>
@@ -117,8 +117,8 @@
 
   <hmtx>
     <mtx name=".notdef" width="500" lsb="93"/>
-    <mtx name="1em" width="1000" lsb="93"/>
-    <mtx name="3em" width="3000" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="100"/>
+    <mtx name="3em" width="3000" lsb="100"/>
   </hmtx>
 
   <cmap>
@@ -138,8 +138,22 @@
 
   <glyf>
     <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="500" y="1000" on="1" />
+        <pt x="1000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="3000" yMax="3000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1500" y="3000" on="1" />
+        <pt x="3000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
   </glyf>
 
   <name>
diff --git a/tests/tests/graphics/assets/fonts/measurement/bbox.ttf b/tests/tests/graphics/assets/fonts/measurement/bbox.ttf
new file mode 100644
index 0000000..c89c59c
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/measurement/bbox.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/measurement/bbox.ttx b/tests/tests/graphics/assets/fonts/measurement/bbox.ttx
new file mode 100644
index 0000000..e7d34bd
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/measurement/bbox.ttx
@@ -0,0 +1,265 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1emx1em"/>
+    <GlyphID id="2" name="2emx2em"/>
+    <GlyphID id="3" name="3emx3em"/>
+    <GlyphID id="4" name="2emx2em_lsb_1em"/>
+    <GlyphID id="5" name="1emx1em_y1em_origin"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="1.0"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1emx1em" width="1000" lsb="0"/>
+    <mtx name="2emx2em" width="2000" lsb="0"/>
+    <mtx name="3emx3em" width="3000" lsb="0"/>
+    <mtx name="2emx2em_lsb_1em" width="2000" lsb="1000"/>
+    <mtx name="1emx1em_y1em_origin" width="1000" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 format="12" reserved="0" length="3" nGroups="6" platformID="3" platEncID="1" language="0">
+      <map code="0x0028" name="1emx1em" />
+      <map code="0x0061" name="1emx1em" />
+      <map code="0x0062" name="2emx2em" />
+      <map code="0x0063" name="3emx3em" />
+      <map code="0x0064" name="2emx2em_lsb_1em" />
+      <map code="0x0065" name="1emx1em_y1em_origin" />
+    </cmap_format_12>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1emx1em" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="500" y="1000" on="1" />
+        <pt x="1000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="2emx2em" xMin="0" yMin="0" xMax="2000" yMax="2000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="2000" on="1" />
+        <pt x="2000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="3emx3em" xMin="0" yMin="0" xMax="3000" yMax="3000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1500" y="3000" on="1" />
+        <pt x="3000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="2emx2em_lsb_1em" xMin="0" yMin="0" xMax="2000" yMax="2000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="2000" on="1" />
+        <pt x="2000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="1emx1em_y1em_origin" xMin="0" yMin="1000" xMax="1000" yMax="2000">
+      <contour>
+        <pt x="0" y="1000" on="1" />
+        <pt x="500" y="2000" on="1" />
+        <pt x="1000" y="1000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <GSUB>
+  <Version value="0x00010000"/>
+  <ScriptList>
+    <ScriptRecord index="0">
+      <ScriptTag value="latn"/>
+      <Script>
+        <DefaultLangSys>
+          <ReqFeatureIndex value="65535"/>
+          <FeatureIndex index="0" value="0"/>
+        </DefaultLangSys>
+      </Script>
+    </ScriptRecord>
+  </ScriptList>
+  <FeatureList>
+    <FeatureRecord index="0">
+      <FeatureTag value="rtlm"/>
+      <Feature>
+        <LookupListIndex index="0" value="0"/>
+      </Feature>
+    </FeatureRecord>
+  </FeatureList>
+  <LookupList>
+    <Lookup index="0">
+      <LookupType value="1"/>
+      <LookupFlag value="0"/>
+      <SingleSubst index="0" Format="2">
+        <Substitution in="1emx1em" out="3emx3em" />
+      </SingleSubst>
+    </Lookup>
+  </LookupList>
+
+  </GSUB>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2017 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
index f84658f..7d177e5 100644
--- a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
@@ -69,6 +69,10 @@
         "VK_KHR_swapchain",
 };
 
+static const char* blacklistedDeviceExtensions[] = {
+        "VK_KHR_performance_query",
+};
+
 static bool enumerateInstanceExtensions(std::vector<VkExtensionProperties>* extensions) {
     VkResult result;
 
@@ -191,6 +195,11 @@
     std::vector<VkExtensionProperties> supportedDeviceExtensions;
     ASSERT(enumerateDeviceExtensions(mGpu, &supportedDeviceExtensions));
 
+    // Fail if the blacklisted extensions are advertised as supported
+    for (const auto extension : blacklistedDeviceExtensions) {
+        ASSERT(!hasExtension(extension, supportedDeviceExtensions));
+    }
+
     std::vector<const char*> enabledDeviceExtensions;
     for (const auto extension : requiredDeviceExtensions) {
         ASSERT(hasExtension(extension, supportedDeviceExtensions));
@@ -250,7 +259,8 @@
 SwapchainInfo::SwapchainInfo(const DeviceInfo* const deviceInfo)
       : mDeviceInfo(deviceInfo),
         mFormat(VK_FORMAT_UNDEFINED),
-        mDisplaySize({0, 0}),
+        mSurfaceSize({0, 0}),
+        mImageSize({0, 0}),
         mSwapchain(VK_NULL_HANDLE),
         mSwapchainLength(0) {}
 
@@ -296,7 +306,7 @@
     ASSERT(formatIndex < formatCount);
 
     mFormat = formats[formatIndex].format;
-    mDisplaySize = surfaceCapabilities.currentExtent;
+    mImageSize = mSurfaceSize = surfaceCapabilities.currentExtent;
 
     VkSurfaceTransformFlagBitsKHR preTransform =
             (setPreTransform ? surfaceCapabilities.currentTransform
@@ -309,7 +319,7 @@
          (VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR |
           VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR |
           VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR)) != 0) {
-        std::swap(mDisplaySize.width, mDisplaySize.height);
+        std::swap(mImageSize.width, mImageSize.height);
     }
 
     if (outPreTransformHint) {
@@ -325,7 +335,7 @@
             .minImageCount = surfaceCapabilities.minImageCount,
             .imageFormat = mFormat,
             .imageColorSpace = formats[formatIndex].colorSpace,
-            .imageExtent = mDisplaySize,
+            .imageExtent = mImageSize,
             .imageArrayLayers = 1,
             .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
             .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
@@ -474,8 +484,8 @@
                 .renderPass = mRenderPass,
                 .attachmentCount = 1,
                 .pAttachments = &mImageViews[i],
-                .width = mSwapchainInfo->displaySize().width,
-                .height = mSwapchainInfo->displaySize().height,
+                .width = mSwapchainInfo->imageSize().width,
+                .height = mSwapchainInfo->imageSize().height,
                 .layers = 1,
         };
         VK_CALL(vkCreateFramebuffer(mDeviceInfo->device(), &framebufferCreateInfo, nullptr,
@@ -604,8 +614,8 @@
     const VkViewport viewports = {
             .x = 0.0f,
             .y = 0.0f,
-            .width = (float)mSwapchainInfo->displaySize().width,
-            .height = (float)mSwapchainInfo->displaySize().height,
+            .width = (float)mSwapchainInfo->imageSize().width,
+            .height = (float)mSwapchainInfo->imageSize().height,
             .minDepth = 0.0f,
             .maxDepth = 1.0f,
     };
@@ -615,7 +625,7 @@
                             .x = 0,
                             .y = 0,
                     },
-            .extent = mSwapchainInfo->displaySize(),
+            .extent = mSwapchainInfo->imageSize(),
     };
     const VkPipelineViewportStateCreateInfo viewportInfo = {
             .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
@@ -793,7 +803,7 @@
                                                 .x = 0,
                                                 .y = 0,
                                         },
-                                .extent = mSwapchainInfo->displaySize(),
+                                .extent = mSwapchainInfo->imageSize(),
                         },
                 .clearValueCount = 1,
                 .pClearValues = &clearVals,
diff --git a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h
index 743c124..753df15 100644
--- a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h
+++ b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h
@@ -58,7 +58,8 @@
     ~SwapchainInfo();
     VkTestResult init(bool setPreTransform, int* outPreTransformHint);
     VkFormat format() const { return mFormat; }
-    VkExtent2D displaySize() const { return mDisplaySize; }
+    VkExtent2D surfaceSize() const { return mSurfaceSize; }
+    VkExtent2D imageSize() const { return mImageSize; }
     VkSwapchainKHR swapchain() const { return mSwapchain; }
     uint32_t swapchainLength() const { return mSwapchainLength; }
 
@@ -66,7 +67,8 @@
     const DeviceInfo* const mDeviceInfo;
 
     VkFormat mFormat;
-    VkExtent2D mDisplaySize;
+    VkExtent2D mSurfaceSize;
+    VkExtent2D mImageSize;
     VkSwapchainKHR mSwapchain;
     uint32_t mSwapchainLength;
 };
diff --git a/tests/tests/graphics/jni/VulkanTestHelpers.cpp b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
index b549b97..d748306 100644
--- a/tests/tests/graphics/jni/VulkanTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
@@ -102,18 +102,8 @@
       .engineVersion = VK_MAKE_VERSION(1, 0, 0),
       .apiVersion = VK_MAKE_VERSION(1, 1, 0),
   };
-  std::vector<const char *> instanceExt, deviceExt;
+  std::vector<const char *> instanceExt;
   instanceExt.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
-  instanceExt.push_back(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME);
-  instanceExt.push_back(VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME);
-  instanceExt.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME);
-  deviceExt.push_back(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME);
   VkInstanceCreateInfo createInfo = {
       .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
       .pNext = nullptr,
@@ -131,12 +121,37 @@
   ASSERT(status == VK_SUCCESS || status == VK_INCOMPLETE);
   ASSERT(gpuCount > 0);
 
+  VkPhysicalDeviceProperties physicalDeviceProperties;
+  vkGetPhysicalDeviceProperties(mGpu, &physicalDeviceProperties);
+  std::vector<const char *> deviceExt;
+  if (physicalDeviceProperties.apiVersion < VK_API_VERSION_1_1) {
+      deviceExt.push_back(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
+      deviceExt.push_back(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME);
+      deviceExt.push_back(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
+      deviceExt.push_back(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME);
+      deviceExt.push_back(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME);
+  }
+  deviceExt.push_back(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME);
+  deviceExt.push_back(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME);
+
   std::vector<VkExtensionProperties> supportedDeviceExtensions;
   ASSERT(enumerateDeviceExtensions(mGpu, &supportedDeviceExtensions));
   for (const auto extension : deviceExt) {
       ASSERT(hasExtension(extension, supportedDeviceExtensions));
   }
 
+  const VkPhysicalDeviceExternalSemaphoreInfo externalSemaphoreInfo = {
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
+          nullptr,
+          VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+  };
+  VkExternalSemaphoreProperties externalSemaphoreProperties;
+  vkGetPhysicalDeviceExternalSemaphoreProperties(mGpu, &externalSemaphoreInfo,
+                                                 &externalSemaphoreProperties);
+
+  ASSERT(externalSemaphoreProperties.externalSemaphoreFeatures &
+         VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+
   uint32_t queueFamilyCount = 0;
   vkGetPhysicalDeviceQueueFamilyProperties(mGpu, &queueFamilyCount, nullptr);
   ASSERT(queueFamilyCount != 0);
@@ -178,10 +193,37 @@
 
   VK_CALL(vkCreateDevice(mGpu, &deviceCreateInfo, nullptr, &mDevice));
 
-  mPfnGetAndroidHardwareBufferPropertiesANDROID =
-      (PFN_vkGetAndroidHardwareBufferPropertiesANDROID)vkGetDeviceProcAddr(
-          mDevice, "vkGetAndroidHardwareBufferPropertiesANDROID");
-  ASSERT(mPfnGetAndroidHardwareBufferPropertiesANDROID);
+  if (physicalDeviceProperties.apiVersion < VK_API_VERSION_1_1) {
+      mPfnBindImageMemory2 =
+              (PFN_vkBindImageMemory2)vkGetDeviceProcAddr(mDevice, "vkBindImageMemory2KHR");
+      mPfnGetImageMemoryRequirements2 = (PFN_vkGetImageMemoryRequirements2)
+              vkGetDeviceProcAddr(mDevice, "vkGetImageMemoryRequirements2KHR");
+      mPfnCreateSamplerYcbcrConversion = (PFN_vkCreateSamplerYcbcrConversion)
+              vkGetDeviceProcAddr(mDevice, "vkCreateSamplerYcbcrConversionKHR");
+      mPfnDestroySamplerYcbcrConversion = (PFN_vkDestroySamplerYcbcrConversion)
+              vkGetDeviceProcAddr(mDevice, "vkDestroySamplerYcbcrConversionKHR");
+  } else {
+      mPfnBindImageMemory2 =
+              (PFN_vkBindImageMemory2)vkGetDeviceProcAddr(mDevice, "vkBindImageMemory2");
+      mPfnGetImageMemoryRequirements2 = (PFN_vkGetImageMemoryRequirements2)
+              vkGetDeviceProcAddr(mDevice, "vkGetImageMemoryRequirements2");
+      mPfnCreateSamplerYcbcrConversion = (PFN_vkCreateSamplerYcbcrConversion)
+              vkGetDeviceProcAddr(mDevice, "vkCreateSamplerYcbcrConversion");
+      mPfnDestroySamplerYcbcrConversion = (PFN_vkDestroySamplerYcbcrConversion)
+              vkGetDeviceProcAddr(mDevice, "vkDestroySamplerYcbcrConversion");
+  }
+  ASSERT(mPfnBindImageMemory2);
+  ASSERT(mPfnGetImageMemoryRequirements2);
+  ASSERT(mPfnCreateSamplerYcbcrConversion);
+  ASSERT(mPfnDestroySamplerYcbcrConversion);
+
+  mPfnGetAndroidHardwareBufferProperties = (PFN_vkGetAndroidHardwareBufferPropertiesANDROID)
+          vkGetDeviceProcAddr(mDevice, "vkGetAndroidHardwareBufferPropertiesANDROID");
+  ASSERT(mPfnGetAndroidHardwareBufferProperties);
+
+  mPfnImportSemaphoreFd =
+          (PFN_vkImportSemaphoreFdKHR)vkGetDeviceProcAddr(mDevice, "vkImportSemaphoreFdKHR");
+  ASSERT(mPfnImportSemaphoreFd);
 
   VkPhysicalDeviceSamplerYcbcrConversionFeaturesKHR ycbcrFeatures{
       .sType =
@@ -192,11 +234,7 @@
       .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR,
       .pNext = &ycbcrFeatures,
   };
-  PFN_vkGetPhysicalDeviceFeatures2KHR getFeatures =
-      (PFN_vkGetPhysicalDeviceFeatures2KHR)vkGetInstanceProcAddr(
-          mInstance, "vkGetPhysicalDeviceFeatures2KHR");
-  ASSERT(getFeatures);
-  getFeatures(mGpu, &physicalDeviceFeatures);
+  vkGetPhysicalDeviceFeatures2(mGpu, &physicalDeviceFeatures);
   ASSERT(ycbcrFeatures.samplerYcbcrConversion == VK_TRUE);
 
   vkGetDeviceQueue(mDevice, 0, 0, &mQueue);
@@ -255,8 +293,7 @@
       .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
       .pNext = &formatInfo,
   };
-  VK_CALL(mInit->getHardwareBufferPropertiesFn()(mInit->device(), buffer,
-                                                 &properties));
+  VK_CALL(mInit->mPfnGetAndroidHardwareBufferProperties(mInit->device(), buffer, &properties));
   ASSERT(useExternalFormat || formatInfo.format != VK_FORMAT_UNDEFINED);
   // Create an image to bind to our AHardwareBuffer.
   VkExternalFormatANDROID externalFormat{
@@ -319,11 +356,7 @@
   bindImageInfo.memory = mMemory;
   bindImageInfo.memoryOffset = 0;
 
-  PFN_vkBindImageMemory2KHR bindImageMemory =
-      (PFN_vkBindImageMemory2KHR)vkGetDeviceProcAddr(mInit->device(),
-                                                     "vkBindImageMemory2KHR");
-  ASSERT(bindImageMemory);
-  VK_CALL(bindImageMemory(mInit->device(), 1, &bindImageInfo));
+  VK_CALL(mInit->mPfnBindImageMemory2(mInit->device(), 1, &bindImageInfo));
 
   VkImageMemoryRequirementsInfo2 memReqsInfo;
   memReqsInfo.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2;
@@ -338,11 +371,7 @@
   memReqs.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2;
   memReqs.pNext = &dedicatedMemReqs;
 
-  PFN_vkGetImageMemoryRequirements2KHR getImageMemoryRequirements =
-      (PFN_vkGetImageMemoryRequirements2KHR)vkGetDeviceProcAddr(
-          mInit->device(), "vkGetImageMemoryRequirements2KHR");
-  ASSERT(getImageMemoryRequirements);
-  getImageMemoryRequirements(mInit->device(), &memReqsInfo, &memReqs);
+  mInit->mPfnGetImageMemoryRequirements2(mInit->device(), &memReqsInfo, &memReqs);
   ASSERT(VK_TRUE == dedicatedMemReqs.prefersDedicatedAllocation);
   ASSERT(VK_TRUE == dedicatedMemReqs.requiresDedicatedAllocation);
 
@@ -359,12 +388,8 @@
         .chromaFilter = VK_FILTER_NEAREST,
         .forceExplicitReconstruction = VK_FALSE,
     };
-    PFN_vkCreateSamplerYcbcrConversionKHR createSamplerYcbcrConversion =
-        (PFN_vkCreateSamplerYcbcrConversionKHR)vkGetDeviceProcAddr(
-            mInit->device(), "vkCreateSamplerYcbcrConversionKHR");
-    ASSERT(createSamplerYcbcrConversion);
-    VK_CALL(createSamplerYcbcrConversion(mInit->device(), &conversionCreateInfo,
-                                         nullptr, &mConversion));
+    VK_CALL(mInit->mPfnCreateSamplerYcbcrConversion(mInit->device(), &conversionCreateInfo, nullptr,
+                                                    &mConversion));
   }
   VkSamplerYcbcrConversionInfo samplerConversionInfo{
       .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
@@ -431,12 +456,7 @@
         .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
         .fd = syncFd,
     };
-
-    PFN_vkImportSemaphoreFdKHR importSemaphoreFd =
-        (PFN_vkImportSemaphoreFdKHR)vkGetDeviceProcAddr(
-            mInit->device(), "vkImportSemaphoreFdKHR");
-    ASSERT(importSemaphoreFd);
-    VK_CALL(importSemaphoreFd(mInit->device(), &importSemaphoreInfo));
+    VK_CALL(mInit->mPfnImportSemaphoreFd(mInit->device(), &importSemaphoreInfo));
   }
 
   return true;
@@ -452,10 +472,7 @@
     mSampler = VK_NULL_HANDLE;
   }
   if (mConversion != VK_NULL_HANDLE) {
-    PFN_vkDestroySamplerYcbcrConversionKHR destroySamplerYcbcrConversion =
-        (PFN_vkDestroySamplerYcbcrConversionKHR)vkGetDeviceProcAddr(
-            mInit->device(), "vkDestroySamplerYcbcrConversionKHR");
-    destroySamplerYcbcrConversion(mInit->device(), mConversion, nullptr);
+    mInit->mPfnDestroySamplerYcbcrConversion(mInit->device(), mConversion, nullptr);
   }
   if (mMemory != VK_NULL_HANDLE) {
     vkFreeMemory(mInit->device(), mMemory, nullptr);
diff --git a/tests/tests/graphics/jni/VulkanTestHelpers.h b/tests/tests/graphics/jni/VulkanTestHelpers.h
index c7cc1ee..773f9a7 100644
--- a/tests/tests/graphics/jni/VulkanTestHelpers.h
+++ b/tests/tests/graphics/jni/VulkanTestHelpers.h
@@ -36,14 +36,16 @@
   VkQueue queue() { return mQueue; }
   VkPhysicalDevice gpu() { return mGpu; }
   uint32_t queueFamilyIndex() { return mQueueFamilyIndex; }
-  PFN_vkGetAndroidHardwareBufferPropertiesANDROID
-    getHardwareBufferPropertiesFn() {
-      return mPfnGetAndroidHardwareBufferPropertiesANDROID;
-    }
-
   uint32_t findMemoryType(uint32_t memoryTypeBitsRequirement,
                           VkFlags requirementsMask);
 
+  PFN_vkBindImageMemory2 mPfnBindImageMemory2 = nullptr;
+  PFN_vkGetImageMemoryRequirements2 mPfnGetImageMemoryRequirements2 = nullptr;
+  PFN_vkCreateSamplerYcbcrConversion mPfnCreateSamplerYcbcrConversion = nullptr;
+  PFN_vkDestroySamplerYcbcrConversion mPfnDestroySamplerYcbcrConversion = nullptr;
+  PFN_vkImportSemaphoreFdKHR mPfnImportSemaphoreFd = nullptr;
+  PFN_vkGetAndroidHardwareBufferPropertiesANDROID mPfnGetAndroidHardwareBufferProperties = nullptr;
+
 private:
   VkInstance mInstance = VK_NULL_HANDLE;
   VkPhysicalDevice mGpu = VK_NULL_HANDLE;
@@ -51,8 +53,6 @@
   VkQueue mQueue = VK_NULL_HANDLE;
   uint32_t mQueueFamilyIndex = 0;
   VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
-  PFN_vkGetAndroidHardwareBufferPropertiesANDROID
-      mPfnGetAndroidHardwareBufferPropertiesANDROID = nullptr;
 };
 
 // Provides import of AHardwareBuffer.
diff --git a/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp b/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp
index c9f557f..4bc1f98 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp
@@ -29,14 +29,16 @@
 
 namespace {
 
-jboolean validatePixelValues(JNIEnv* env, jboolean setPreTransform, jint preTransformHint) {
+jboolean validatePixelValues(JNIEnv* env, jint width, jint height, jboolean setPreTransform,
+                             jint preTransformHint) {
     jclass clazz = env->FindClass("android/graphics/cts/VulkanPreTransformTest");
-    jmethodID mid = env->GetStaticMethodID(clazz, "validatePixelValuesAfterRotation", "(ZI)Z");
+    jmethodID mid = env->GetStaticMethodID(clazz, "validatePixelValuesAfterRotation", "(IIZI)Z");
     if (mid == 0) {
         ALOGE("Failed to find method ID");
         return false;
     }
-    return env->CallStaticBooleanMethod(clazz, mid, setPreTransform, preTransformHint);
+    return env->CallStaticBooleanMethod(clazz, mid, width, height, setPreTransform,
+                                        preTransformHint);
 }
 
 void createNativeTest(JNIEnv* env, jclass /*clazz*/, jobject jAssetManager, jobject jSurface,
@@ -71,7 +73,10 @@
         }
     }
 
-    ASSERT(validatePixelValues(env, setPreTransform, preTransformHint), "Not properly rotated");
+    const VkExtent2D surfaceSize = swapchainInfo.surfaceSize();
+    ASSERT(validatePixelValues(env, surfaceSize.width, surfaceSize.height, setPreTransform,
+                               preTransformHint),
+           "Not properly rotated");
 }
 
 const std::array<JNINativeMethod, 1> JNI_METHODS = {{
diff --git a/tests/tests/graphics/res/font/a3em.ttf b/tests/tests/graphics/res/font/a3em.ttf
index a601ce2..e7814db 100644
--- a/tests/tests/graphics/res/font/a3em.ttf
+++ b/tests/tests/graphics/res/font/a3em.ttf
Binary files differ
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
index 371a637..3e1d527 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -49,6 +50,8 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -147,7 +150,7 @@
             InputStream is = obtainInputStream(RES_IDS[i]);
             try {
                 BitmapRegionDecoder decoder =
-                        BitmapRegionDecoder.newInstance(is, false);
+                        BitmapRegionDecoder.newInstance(is);
                 assertEquals(WIDTHS[i], decoder.getWidth());
                 assertEquals(HEIGHTS[i], decoder.getHeight());
             } catch (IOException e) {
@@ -167,7 +170,7 @@
             byte[] imageData = obtainByteArray(RES_IDS[i]);
             try {
                 BitmapRegionDecoder decoder = BitmapRegionDecoder
-                        .newInstance(imageData, 0, imageData.length, false);
+                        .newInstance(imageData, 0, imageData.length);
                 assertEquals(WIDTHS[i], decoder.getWidth());
                 assertEquals(HEIGHTS[i], decoder.getHeight());
             } catch (IOException e) {
@@ -182,15 +185,14 @@
         for (int i = 0; i < RES_IDS.length; ++i) {
             String filepath = obtainPath(i);
             ParcelFileDescriptor pfd = obtainParcelDescriptor(filepath);
-            FileDescriptor fd = pfd.getFileDescriptor();
             try {
                 BitmapRegionDecoder decoder1 =
-                        BitmapRegionDecoder.newInstance(filepath, false);
+                        BitmapRegionDecoder.newInstance(filepath);
                 assertEquals(WIDTHS[i], decoder1.getWidth());
                 assertEquals(HEIGHTS[i], decoder1.getHeight());
 
                 BitmapRegionDecoder decoder2 =
-                        BitmapRegionDecoder.newInstance(fd, false);
+                        BitmapRegionDecoder.newInstance(pfd);
                 assertEquals(WIDTHS[i], decoder2.getWidth());
                 assertEquals(HEIGHTS[i], decoder2.getHeight());
             } catch (IOException e) {
@@ -211,7 +213,7 @@
                     opts.inPreferredConfig = COLOR_CONFIGS[k];
 
                     InputStream is1 = obtainInputStream(RES_IDS[i]);
-                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
                     InputStream is2 = obtainInputStream(RES_IDS[i]);
                     Bitmap wholeImage = BitmapFactory.decodeStream(is2, null, opts);
 
@@ -239,7 +241,7 @@
                     opts.inBitmap = null;
 
                     InputStream is1 = obtainInputStream(RES_IDS[i]);
-                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
                     InputStream is2 = obtainInputStream(RES_IDS[i]);
                     Bitmap wholeImage = BitmapFactory.decodeStream(is2, null, opts);
 
@@ -271,7 +273,7 @@
 
                     byte[] imageData = obtainByteArray(RES_IDS[i]);
                     BitmapRegionDecoder decoder = BitmapRegionDecoder
-                            .newInstance(imageData, 0, imageData.length, false);
+                            .newInstance(imageData, 0, imageData.length);
                     Bitmap wholeImage = BitmapFactory.decodeByteArray(imageData,
                             0, imageData.length, opts);
 
@@ -298,8 +300,7 @@
                     opts.inSampleSize = SAMPLESIZES[j];
                     opts.inPreferredConfig = COLOR_CONFIGS[k];
 
-                    BitmapRegionDecoder decoder =
-                        BitmapRegionDecoder.newInstance(filepath, false);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(filepath);
                     Bitmap wholeImage = BitmapFactory.decodeFile(filepath, opts);
                     if (RES_IDS[i] == R.drawable.webp_test && COLOR_CONFIGS[k] == Config.RGB_565) {
                         compareRegionByRegion(decoder, opts, MSE_MARGIN_WEB_P_CONFIG_RGB_565,
@@ -309,10 +310,7 @@
                     }
 
                     ParcelFileDescriptor pfd1 = obtainParcelDescriptor(filepath);
-                    FileDescriptor fd1 = pfd1.getFileDescriptor();
-                    decoder = BitmapRegionDecoder.newInstance(fd1, false);
-                    ParcelFileDescriptor pfd2 = obtainParcelDescriptor(filepath);
-                    FileDescriptor fd2 = pfd2.getFileDescriptor();
+                    decoder = BitmapRegionDecoder.newInstance(pfd1);
                     if (RES_IDS[i] == R.drawable.webp_test && COLOR_CONFIGS[k] == Config.RGB_565) {
                         compareRegionByRegion(decoder, opts, MSE_MARGIN_WEB_P_CONFIG_RGB_565,
                                               wholeImage);
@@ -328,7 +326,7 @@
     @Test
     public void testRecycle() throws IOException {
         InputStream is = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
         decoder.recycle();
         assertTrue(decoder.isRecycled());
         try {
@@ -369,7 +367,7 @@
 
         for (int i = 0; i < NUM_TEST_IMAGES; i++) {
             InputStream is = obtainInputStream(RES_IDS[i]);
-            BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+            BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
             for (int j = 0; j < SAMPLESIZES.length; j++) {
                 int sampleSize = SAMPLESIZES[j];
                 defaultOpts.inSampleSize = sampleSize;
@@ -437,7 +435,7 @@
         BitmapFactory.Options options = new BitmapFactory.Options();
         options.inPreferredConfig = Bitmap.Config.HARDWARE;
         InputStream is = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
         Bitmap hardwareBitmap = decoder.decodeRegion(new Rect(0, 0, 10, 10), options);
         assertNotNull(hardwareBitmap);
         // Test that checks that correct bitmap was obtained is in uirendering/HardwareBitmapTests
@@ -454,7 +452,7 @@
                     opts.inPreferredConfig = COLOR_CONFIGS[k];
 
                     InputStream is1 = obtainInputStream(RES_IDS[i]);
-                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
                     Bitmap region = decoder.decodeRegion(
                             new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
                     decoder.recycle();
@@ -477,7 +475,7 @@
 
                     String assetName = ASSET_NAMES[i];
                     InputStream is1 = obtainInputStream(assetName);
-                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
                     Bitmap region = decoder.decodeRegion(
                             new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts);
                     decoder.recycle();
@@ -501,7 +499,7 @@
 
         // sRGB
         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(
-                obtainInputStream(ASSET_NAMES[3]), false);
+                obtainInputStream(ASSET_NAMES[3]));
         Bitmap region = decoder.decodeRegion(
                 new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts);
         decoder.recycle();
@@ -509,7 +507,7 @@
         assertEquals(ColorSpace.get(ColorSpace.Named.SRGB), region.getColorSpace());
 
         // DisplayP3
-        decoder = BitmapRegionDecoder.newInstance(obtainInputStream(ASSET_NAMES[1]), false);
+        decoder = BitmapRegionDecoder.newInstance(obtainInputStream(ASSET_NAMES[1]));
         region = decoder.decodeRegion(new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts);
         decoder.recycle();
 
@@ -525,7 +523,7 @@
                 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
 
                 InputStream is1 = obtainInputStream(RES_IDS[i]);
-                BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+                BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
                 Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
                 decoder.recycle();
 
@@ -542,7 +540,7 @@
         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB);
 
         InputStream is1 = obtainInputStream(ASSET_NAMES[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
         Bitmap region = decoder.decodeRegion(new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts);
         decoder.recycle();
 
@@ -557,7 +555,7 @@
         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB);
 
         InputStream is1 = obtainInputStream(ASSET_NAMES[1]); // Display P3
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
         Bitmap region = decoder.decodeRegion(new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts);
         decoder.recycle();
 
@@ -570,7 +568,7 @@
         // This image normally decodes to F16, but if there is an inBitmap,
         // decoding will match the Config of that Bitmap.
         InputStream is = obtainInputStream(ASSET_NAMES[0]); // F16
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
 
         Options opts = new BitmapFactory.Options();
         for (Bitmap.Config config : new Bitmap.Config[] {
@@ -597,7 +595,7 @@
         Options opts = new BitmapFactory.Options();
         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.CIE_LAB);
         InputStream is1 = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
     }
 
@@ -610,7 +608,7 @@
                 x -> Math.pow(x, 1.0f / 2.2f), x -> Math.pow(x, 2.2f),
                 0, 1);
         InputStream is1 = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
     }
 
@@ -621,7 +619,7 @@
                 .copy(Config.HARDWARE, false);
         opts.inBitmap = bitmap;
         InputStream is = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
         decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
     }
 
@@ -633,7 +631,7 @@
 
         opts.inBitmap = bitmap;
         InputStream is = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
         assertThrows(IllegalArgumentException.class, () -> {
             decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
         });
@@ -642,7 +640,7 @@
     @Test
     public void testHeif() throws IOException {
         InputStream is = obtainInputStream(R.raw.heifwriter_input);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), null);
         assertNotNull(region);
 
@@ -652,6 +650,120 @@
         assertNotNull(full);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void testNullParcelFileDescriptor() throws IOException {
+        ParcelFileDescriptor pfd = null;
+        BitmapRegionDecoder.newInstance(pfd);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullFileDescriptor() throws IOException {
+        FileDescriptor fd = null;
+        BitmapRegionDecoder.newInstance(fd, false);
+    }
+
+    @Test
+    public void testNullInputStream() throws IOException {
+        InputStream is = null;
+        assertNull(BitmapRegionDecoder.newInstance(is));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullPathName() throws IOException {
+        String pathName = null;
+        BitmapRegionDecoder.newInstance(pathName);
+    }
+
+    @Test(expected = IOException.class)
+    public void testEmptyPathName() throws IOException {
+        String pathName = "";
+        BitmapRegionDecoder.newInstance(pathName);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullByteArray() throws IOException {
+        byte[] data = null;
+        BitmapRegionDecoder.newInstance(data, 0, 0);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testNegativeOffset() throws IOException {
+        byte[] data = new byte[10];
+        BitmapRegionDecoder.newInstance(data, -1, 10);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testNegativeLength() throws IOException {
+        byte[] data = new byte[10];
+        BitmapRegionDecoder.newInstance(data, 0, -10);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testTooLong() throws IOException {
+        byte[] data = new byte[10];
+        BitmapRegionDecoder.newInstance(data, 1, 10);
+    }
+
+    @Test(expected = IOException.class)
+    public void testEmptyByteArray() throws IOException {
+        byte[] data = new byte[0];
+        BitmapRegionDecoder.newInstance(data, 0, 0);
+    }
+
+    @Test(expected = IOException.class)
+    public void testEmptyInputStream() throws IOException {
+        InputStream is = new InputStream() {
+            @Override
+            public int read() {
+                return -1;
+            }
+        };
+        BitmapRegionDecoder.newInstance(is);
+    }
+
+    private static File createEmptyFile() throws IOException {
+        File dir = InstrumentationRegistry.getTargetContext().getFilesDir();
+        dir.mkdirs();
+        return File.createTempFile("emptyFile", "tmp", dir);
+    }
+
+    @Test
+    public void testEmptyFile() throws IOException {
+        File file = createEmptyFile();
+        String pathName = file.getAbsolutePath();
+        assertThrows(IOException.class, () -> {
+            BitmapRegionDecoder.newInstance(pathName);
+        });
+        file.delete();
+    }
+
+    @Test
+    public void testEmptyFileDescriptor() throws IOException {
+        File file = createEmptyFile();
+        FileInputStream fileStream = new FileInputStream(file);
+        FileDescriptor fd = fileStream.getFD();
+        assertThrows(IOException.class, () -> {
+            BitmapRegionDecoder.newInstance(fd, false);
+        });
+        file.delete();
+    }
+
+    @Test
+    public void testEmptyParcelFileDescriptor() throws IOException, FileNotFoundException {
+        File file = createEmptyFile();
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
+                ParcelFileDescriptor.MODE_READ_ONLY);
+        assertThrows(IOException.class, () -> {
+            BitmapRegionDecoder.newInstance(pfd);
+        });
+        file.delete();
+    }
+
+    @Test(expected = IOException.class)
+    public void testInvalidFileDescriptor() throws IOException {
+        BitmapRegionDecoder.newInstance(new FileDescriptor(), false);
+    }
+
     private void compareRegionByRegion(BitmapRegionDecoder decoder,
             Options opts, int mseMargin, Bitmap wholeImage) {
         int width = decoder.getWidth();
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
index d8fad93..94271f1 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
@@ -68,6 +68,7 @@
 import java.nio.ShortBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -675,6 +676,103 @@
         }
     }
 
+    private void assertMatches(HardwareBuffer hwBuffer, HardwareBuffer hwBuffer2) {
+        assertEquals(hwBuffer, hwBuffer2);
+        assertEquals(hwBuffer.hashCode(), hwBuffer2.hashCode());
+        assertEquals(hwBuffer.getWidth(), hwBuffer2.getWidth());
+        assertEquals(hwBuffer.getHeight(), hwBuffer2.getHeight());
+        assertEquals(hwBuffer.getFormat(), hwBuffer2.getFormat());
+        assertEquals(hwBuffer.getLayers(), hwBuffer2.getLayers());
+        assertEquals(hwBuffer.getUsage(), hwBuffer2.getUsage());
+    }
+
+    @Test
+    public void testGetHardwareBufferMatchesWrapped() {
+        try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, false)) {
+            Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
+            assertNotNull(bitmap);
+
+            try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) {
+                assertNotNull(hwBuffer2);
+                assertMatches(hwBuffer, hwBuffer2);
+            }
+            bitmap.recycle();
+        }
+    }
+
+    private static Object[] parametersFor_testGetHardwareBufferConfig() {
+        return new Object[] {Config.ARGB_8888, Config.RGBA_F16, Config.RGB_565};
+    }
+
+    @Test
+    @Parameters(method = "parametersFor_testGetHardwareBufferConfig")
+    public void testGetHardwareBufferConfig(Config config) {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, config);
+        bitmap = bitmap.copy(Config.HARDWARE, false);
+        if (bitmap == null) {
+            fail("Failed to copy to HARDWARE with Config " + config);
+        }
+        try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) {
+            assertNotNull(hwBuffer);
+            assertEquals(hwBuffer.getWidth(), 10);
+            assertEquals(hwBuffer.getHeight(), 10);
+        }
+    }
+
+    @Test
+    public void testGetHardwareBufferTwice() {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        bitmap = bitmap.copy(Config.HARDWARE, false);
+        try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) {
+            assertNotNull(hwBuffer);
+            try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) {
+                assertNotNull(hwBuffer2);
+                assertMatches(hwBuffer, hwBuffer2);
+            }
+        }
+    }
+
+    @Test
+    public void testGetHardwareBufferMatches() {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        bitmap = bitmap.copy(Config.HARDWARE, false);
+        try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) {
+            HashSet<HardwareBuffer> set = new HashSet<HardwareBuffer>();
+            set.add(hwBuffer);
+            assertTrue(set.contains(bitmap.getHardwareBuffer()));
+        }
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetHardwareBufferNonHardware() {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        bitmap.getHardwareBuffer();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetHardwareBufferRecycled() {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        bitmap = bitmap.copy(Config.HARDWARE, false);
+        bitmap.recycle();
+        bitmap.getHardwareBuffer();
+    }
+
+    @Test
+    public void testGetHardwareBufferClosed() {
+        HardwareBuffer hwBuffer = createTestBuffer(128, 128, false);
+        Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
+        assertNotNull(bitmap);
+
+        hwBuffer.close();
+
+        try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) {
+            assertNotNull(hwBuffer2);
+            assertFalse(hwBuffer2.isClosed());
+            assertNotEquals(hwBuffer, hwBuffer2);
+        }
+        bitmap.recycle();
+    }
+
     @Test
     public void testGenerationId() {
         Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
@@ -2301,6 +2399,108 @@
         }
     }
 
+    private static byte[] compressToPng(Bitmap bitmap) {
+        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
+            assertTrue("Failed to encode a Bitmap with Config " + bitmap.getConfig()
+                    + " and ColorSpace " + bitmap.getColorSpace() + "!",
+                    bitmap.compress(CompressFormat.PNG, 100, stream));
+            return stream.toByteArray();
+        } catch (IOException e) {
+            fail("Failed to compress with " + e);
+            return null;
+        }
+    }
+
+    private static Object[] parametersForTestAsShared() {
+        return Utils.crossProduct(Config.values(), getRgbColorSpaces().toArray(new Object[0]));
+    }
+
+    @Test
+    @Parameters(method = "parametersForTestAsShared")
+    public void testAsShared(Config config, ColorSpace colorSpace) {
+        Bitmap original = Bitmap.createBitmap(10, 10,
+                config == Config.HARDWARE ? Config.ARGB_8888 : config, true /*hasAlpha*/,
+                colorSpace);
+        drawGradient(original);
+
+        if (config == Config.HARDWARE) {
+            original = original.copy(Config.HARDWARE, false /*mutable*/);
+        }
+
+        // There's no visible way to test that the memory is allocated in shared memory, but we can
+        // verify that the Bitmaps look the same.
+        Bitmap shared = original.asShared();
+        assertNotNull(shared);
+
+        if (config == Config.HARDWARE) {
+            int expectedFormat = nGetFormat(original);
+            assertEquals(expectedFormat, configToFormat(shared.getConfig()));
+
+            // There's no public way to look at the pixels in the HARDWARE Bitmap, but if we
+            // compress each as a lossless PNG, they should look identical.
+            byte[] origBytes = compressToPng(original);
+            byte[] sharedBytes = compressToPng(shared);
+            assertTrue(Arrays.equals(origBytes, sharedBytes));
+        } else {
+            assertSame(original.getConfig(), shared.getConfig());
+            assertTrue(shared.sameAs(original));
+        }
+        assertSame(original.getColorSpace(), shared.getColorSpace());
+
+        // The Bitmap is already in shared memory, so no work is done.
+        Bitmap shared2 = shared.asShared();
+        assertSame(shared, shared2);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAsSharedRecycled() {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        bitmap.recycle();
+        bitmap.asShared();
+    }
+
+    @Test
+    public void testAsSharedDensity() {
+        DisplayMetrics metrics =
+                InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics();
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        for (int density : new int[] { Bitmap.DENSITY_NONE, metrics.densityDpi,
+                DisplayMetrics.DENSITY_HIGH, DisplayMetrics.DENSITY_DEVICE_STABLE,
+                DisplayMetrics.DENSITY_MEDIUM }) {
+            bitmap.setDensity(density);
+            Bitmap shared = bitmap.asShared();
+            assertEquals(density, shared.getDensity());
+            shared.recycle();
+        }
+    }
+
+    @Test
+    @Parameters({"true", "false"})
+    public void testAsSharedImageDecoder(boolean mutable) {
+        Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        ImageDecoder.Source source = ImageDecoder.createSource(res.getAssets(),
+                "grayscale-16bit-linearSrgb.png");
+        try {
+            Bitmap bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, s) -> {
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SHARED_MEMORY);
+                if (mutable) decoder.setMutableRequired(true);
+            });
+
+            Bitmap shared = bitmap.asShared();
+            if (mutable) {
+                // bitmap is mutable, so asShared must make a copy.
+                assertNotEquals(bitmap, shared);
+                assertTrue(bitmap.sameAs(shared));
+            } else {
+                // bitmap is already immutable and in shared memory, so asShared will return
+                // itself.
+                assertSame(bitmap, shared);
+            }
+        } catch (IOException e) {
+            fail("Failed to decode with " + e);
+        }
+    }
+
     @Test
     public void testNdkFormats() {
         for (ConfigToFormat pair : CONFIG_TO_FORMAT) {
@@ -2565,6 +2765,24 @@
                 || cs == ColorSpace.get(Named.LINEAR_EXTENDED_SRGB);
     }
 
+    // Helper method for populating a Bitmap with interesting pixels for comparison.
+    private static void drawGradient(Bitmap bitmap) {
+        // Use different colors and alphas.
+        Canvas canvas = new Canvas(bitmap);
+        ColorSpace cs = bitmap.getColorSpace();
+        if (cs == null) {
+            assertSame(Config.ALPHA_8, bitmap.getConfig());
+            cs = ColorSpace.get(ColorSpace.Named.SRGB);
+        }
+        long color0 = Color.pack(0, 0, 1, 1, cs);
+        long color1 = Color.pack(1, 0, 0, 0, cs);
+        LinearGradient gradient = new LinearGradient(0, 0, 10, 10, color0, color1,
+                Shader.TileMode.CLAMP);
+        Paint paint = new Paint();
+        paint.setShader(gradient);
+        canvas.drawPaint(paint);
+    }
+
     @Test
     @Parameters(method = "parametersForNdkCompress")
     public void testNdkCompress(CompressFormat format, ColorSpace cs, Config config)
@@ -2574,15 +2792,7 @@
         assertNotNull(bitmap);
 
         {
-            // Use different colors and alphas.
-            Canvas canvas = new Canvas(bitmap);
-            long color0 = Color.pack(0, 0, 1, 1, cs);
-            long color1 = Color.pack(1, 0, 0, 0, cs);
-            LinearGradient gradient = new LinearGradient(0, 0, 10, 10, color0, color1,
-                    Shader.TileMode.CLAMP);
-            Paint paint = new Paint();
-            paint.setShader(gradient);
-            canvas.drawPaint(paint);
+            drawGradient(bitmap);
         }
 
         byte[] storage = new byte[16 * 1024];
@@ -2657,6 +2867,7 @@
     private static native void nFillRgbaHwBuffer(HardwareBuffer hwBuffer);
     private static native void nTestNullBitmap(Bitmap bitmap);
 
+    private static final int ANDROID_BITMAP_FORMAT_NONE = 0;
     static final int ANDROID_BITMAP_FORMAT_RGBA_8888 = 1;
     private static final int ANDROID_BITMAP_FORMAT_RGB_565 = 4;
     private static final int ANDROID_BITMAP_FORMAT_A_8 = 8;
@@ -2672,6 +2883,15 @@
         }
     }
 
+    private static int configToFormat(Config config) {
+        for (ConfigToFormat pair : CONFIG_TO_FORMAT) {
+            if (config == pair.config) {
+                return pair.format;
+            }
+        }
+        return ANDROID_BITMAP_FORMAT_NONE;
+    }
+
     private static final ConfigToFormat[] CONFIG_TO_FORMAT = new ConfigToFormat[] {
         new ConfigToFormat(Bitmap.Config.ARGB_8888, ANDROID_BITMAP_FORMAT_RGBA_8888),
         // ARGB_4444 is deprecated, and createBitmap converts to 8888.
diff --git a/tests/tests/graphics/src/android/graphics/cts/CanvasDrawGlyphsTest.java b/tests/tests/graphics/src/android/graphics/cts/CanvasDrawGlyphsTest.java
new file mode 100644
index 0000000..5c915f3
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/CanvasDrawGlyphsTest.java
@@ -0,0 +1,382 @@
+/*
+ * 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.graphics.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CanvasDrawGlyphsTest {
+    private static final String FONT_PATH = "fonts/draw/draw_glyph_font.ttf";
+    private static final int BMP_WIDTH = 300;
+    private static final int BMP_HEIGHT = 100;
+    private static final float DRAW_ORIGIN_X = 20f;
+    private static final float DRAW_ORIGIN_Y = 70f;
+    private static final float TEXT_SIZE = 50f;  // make 1em = 50px
+    // All glyph in the test font has 1em advance, i.e. TEXT_SIZE.
+    private static final float GLYPH_ADVANCE = TEXT_SIZE;
+    private Font mFont;
+    private Typeface mTypeface;
+
+    @Before
+    public void setup() throws IOException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mFont = new Font.Builder(context.getAssets(), FONT_PATH).build();
+        mTypeface = Typeface.createFromAsset(context.getAssets(), FONT_PATH);
+    }
+
+    private Bitmap drawGlyphsToBitmap(int[] glyphIds, int glyphIdOffset, float[] positions,
+            int positionStart, int glyphCount, Font font, Paint paint) {
+        Bitmap bmp = Bitmap.createBitmap(BMP_WIDTH, BMP_HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bmp);
+        canvas.drawGlyphs(glyphIds, glyphIdOffset, positions, positionStart, glyphCount,
+                font, paint);
+        return bmp;
+    }
+
+    @Test
+    public void drawGlyphs_SameToDrawText() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        Bitmap glyphBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },  // Corresponding to "abcde" in test font
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap textBmp = Bitmap.createBitmap(300, 100, Bitmap.Config.ARGB_8888);
+        Canvas textCanvas = new Canvas(textBmp);
+        paint.setTypeface(mTypeface);
+        textCanvas.drawText("abcde", 0, 5, DRAW_ORIGIN_X, DRAW_ORIGIN_Y, paint);
+
+        assertThat(glyphBmp.sameAs(textBmp)).isTrue();
+    }
+
+    @Test
+    public void drawGlyphs_glyphOffset() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        Bitmap glyphBmp = drawGlyphsToBitmap(
+                new int[] { -1, -1, 1, 2, 3, 4, 5 },  // Skip first two
+                2,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap glyphBmp2 = drawGlyphsToBitmap(
+                new int[] { -1, -1, -1, 1, 2, 3, 4, 5 },  // Skip first three
+                3,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap expectedBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        assertThat(glyphBmp.sameAs(glyphBmp2)).isTrue();
+        assertThat(glyphBmp.sameAs(expectedBmp)).isTrue();
+    }
+
+    @Test
+    public void drawGlyphs_positionOffset() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        Bitmap glyphBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        -1f, -1f,  // Skip first two
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                2,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap glyphBmp2 = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        -1f, -1f, -1f,  // Skip first three
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                3,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap expectedBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        assertThat(glyphBmp.sameAs(glyphBmp2)).isTrue();
+        assertThat(glyphBmp.sameAs(expectedBmp)).isTrue();
+    }
+
+    @Test
+    public void drawGlyphs_glyphCount() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        Bitmap firstThreeBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                3,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap firstTwoBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                2,  // glyph count
+                mFont,
+                paint
+        );
+
+        assertThat(firstThreeBmp.sameAs(firstTwoBmp)).isFalse();
+    }
+
+    @Test
+    public void drawGlyphs_positionDifference() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        float dx = 10f;
+        float dy = 1f;
+        Bitmap bmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + dx, DRAW_ORIGIN_Y + dy,
+                        DRAW_ORIGIN_X + dx * 2, DRAW_ORIGIN_Y + dy * 2,
+                        DRAW_ORIGIN_X + dx * 3, DRAW_ORIGIN_Y + dy * 3,
+                        DRAW_ORIGIN_X + dx * 4, DRAW_ORIGIN_Y + dy * 4,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        dx = 5f;
+        dy = 2f;
+        Bitmap differentGlyphPositionBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + dx, DRAW_ORIGIN_Y + dy,
+                        DRAW_ORIGIN_X + dx * 2, DRAW_ORIGIN_Y + dy * 2,
+                        DRAW_ORIGIN_X + dx * 3, DRAW_ORIGIN_Y + dy * 3,
+                        DRAW_ORIGIN_X + dx * 4, DRAW_ORIGIN_Y + dy * 4,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        assertThat(bmp.sameAs(differentGlyphPositionBmp)).isFalse();
+    }
+
+    @Test
+    public void drawGlyphs_paintEffect() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        Bitmap bmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        paint.setTextSize(TEXT_SIZE * 2f);
+        Bitmap twiceTextSizeBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        assertThat(bmp.sameAs(twiceTextSizeBmp)).isFalse();
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullGlyphIds() {
+        drawGlyphsToBitmap(null, 0, new float[] {}, 0, 0, mFont, new Paint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullPositions() {
+        drawGlyphsToBitmap(new int[] {}, 0, null, 0, 0, mFont, new Paint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullFont() {
+        drawGlyphsToBitmap(new int[] {}, 0, new float[] {}, 0, 0, null, new Paint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullPaint() {
+        drawGlyphsToBitmap(new int[] {}, 0, new float[] {}, 0, 0, mFont, null);
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void negativeGlyphOffset() {
+        drawGlyphsToBitmap(new int[] {}, -1, new float[] {}, 0, 0, mFont, new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void negativePositionOffset() {
+        drawGlyphsToBitmap(new int[] {}, 0, new float[] {}, -1, 0, mFont, new Paint());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void negativeGlyphCount() {
+        drawGlyphsToBitmap(new int[] {}, 0, new float[] {}, 0, -1, mFont, new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void tooShortGlyphIds() {
+        drawGlyphsToBitmap(new int[] {1, 2}, 1, new float[] {}, 0, 2, mFont, new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void tooShortPositions() {
+        drawGlyphsToBitmap(new int[] { 1 }, 0, new float[] { 0f, 0f }, 1, 1, mFont, new Paint());
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
index 92919b9..3d7561c 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
@@ -26,7 +26,6 @@
 import android.graphics.ColorSpace;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,8 +33,11 @@
 import java.util.Arrays;
 import java.util.function.DoubleUnaryOperator;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
 public class ColorSpaceTest {
     // Column-major RGB->XYZ transform matrix for the sRGB color space
     private static final float[] SRGB_TO_XYZ = {
@@ -790,9 +792,154 @@
                         1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4)));
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    @Parameters({"0", "-1", "-50"})
+    public void testInvalidCct(int cct) {
+        ColorSpace.cctToXyz(cct);
+    }
+
+    @Test
+    public void testCctToXyz() {
+        // Verify that range listed as meaningful by the API return float arrays as expected.
+        for (int i = 1667; i <= 25000; i++) {
+            float[] result = ColorSpace.cctToXyz(i);
+            assertNotNull(result);
+            assertEquals(3, result.length);
+        }
+    }
+
+    private static Object[] cctToXyzExpected() {
+        return new Object[] {
+                // ILLUMINANT_A
+                new Object[] { 2856, new float[] { 1.0970824f, 1.0f, 0.3568525f }},
+                // ILLUMINANT_B
+                new Object[] { 4874, new float[] { 0.98355806f, 1.0f, 0.8376475f }},
+                // ILLUMINANT_C
+                new Object[] { 6774, new float[] { 0.9680535f, 1.0f, 1.1603559f }},
+                // ILLUMINANT_D50
+                new Object[] { 5003, new float[] { 0.9811904f, 1.0f, 0.86360276f }},
+                // ILLUMINANT_D55
+                new Object[] { 5503, new float[] { 0.97444946f, 1.0f, 0.9582717f }},
+                // ILLUMINANT_D60
+                new Object[] { 6004, new float[] { 0.9705604f, 1.0f, 1.0441511f }},
+                // ILLUMINANT_D65
+                new Object[] { 6504, new float[] { 0.968573f, 1.0f, 1.1216444f }},
+                // ILLUMINANT_D75
+                new Object[] { 7504, new float[] { 0.9679457f, 1.0f, 1.2551404f }},
+                // ILLUMINANT_E
+                new Object[] { 5454, new float[] { 0.9749648f, 1.0f, 0.9494016f }},
+                // Test a sample of values in the meaningful range according to the API.
+                new Object[] { 1667, new float[] { 1.4014802f, 1.0f, 0.08060435f }},
+                new Object[] { 1668, new float[] { 1.4010513f, 1.0f, 0.08076303f }},
+                new Object[] { 1700, new float[] { 1.3874257f, 1.0f, 0.08596305f }},
+                new Object[] { 1701, new float[] { 1.3870035f, 1.0f, 0.08612958f }},
+                new Object[] { 2020, new float[] { 1.2686056f, 1.0f, 0.14921218f }},
+                new Object[] { 2102, new float[] { 1.2439337f, 1.0f, 0.1678791f }},
+                new Object[] { 2360, new float[] { 1.1796018f, 1.0f, 0.2302558f }},
+                new Object[] { 4688, new float[] { 0.9875373f, 1.0f, 0.79908675f }},
+                new Object[] { 5797, new float[] { 0.97189087f, 1.0f, 1.0097121f }},
+                new Object[] { 7625, new float[] { 0.96806175f, 1.0f, 1.2695707f }},
+                new Object[] { 8222, new float[] { 0.9690009f, 1.0f, 1.3359972f }},
+                new Object[] { 8330, new float[] { 0.9692224f, 1.0f, 1.3472213f }},
+                new Object[] { 9374, new float[] { 0.9718307f, 1.0f, 1.4447508f }},
+                new Object[] { 9604, new float[] { 0.97247595f, 1.0f, 1.4638413f }},
+                new Object[] { 9894, new float[] { 0.9733059f, 1.0f, 1.4868189f }},
+                new Object[] { 10764, new float[] { 0.97584003f, 1.0f, 1.5491791f }},
+                new Object[] { 11735, new float[] { 0.97862047f, 1.0f, 1.6088297f }},
+                new Object[] { 12819, new float[] { 0.98155034f, 1.0f, 1.6653923f }},
+                new Object[] { 13607, new float[] { 0.98353446f, 1.0f, 1.7010691f }},
+                new Object[] { 15185, new float[] { 0.98712224f, 1.0f, 1.7615601f }},
+                new Object[] { 17474, new float[] { 0.9914801f, 1.0f, 1.8297766f }},
+                new Object[] { 18788, new float[] { 0.9935937f, 1.0f, 1.8612393f }},
+                new Object[] { 19119, new float[] { 0.99408686f, 1.0f, 1.8684553f }},
+                new Object[] { 19174, new float[] { 0.99416786f, 1.0f, 1.8696303f }},
+                new Object[] { 19437, new float[] { 0.9945476f, 1.0f, 1.8751476f }},
+                new Object[] { 19533, new float[] { 0.99468416f, 1.0f, 1.8771234f }},
+                new Object[] { 19548, new float[] { 0.99470526f, 1.0f, 1.8774294f }},
+                new Object[] { 19762, new float[] { 0.995005f, 1.0f, 1.8817542f }},
+                new Object[] { 19774, new float[] { 0.9950216f, 1.0f, 1.8819935f }},
+                new Object[] { 20291, new float[] { 0.99572146f, 1.0f, 1.8920314f }},
+                new Object[] { 23018, new float[] { 0.99893945f, 1.0f, 1.9371331f }},
+                new Object[] { 23509, new float[] { 0.999445f, 1.0f, 1.9440757f }},
+                new Object[] { 24761, new float[] { 1.0006485f, 1.0f, 1.9604537f }},
+
+        };
+    }
+
+    @Test
+    @Parameters(method = "cctToXyzExpected")
+    public void testCctToXyzValues(int cct, float[] xyz) {
+        float[] result = ColorSpace.cctToXyz(cct);
+        assertArrayEquals(xyz, result, 1e-3f);
+    }
+
+    private static Object[] chromaticAdaptationNullParameters() {
+        return new Object[] {
+                new Object[] { null, ColorSpace.ILLUMINANT_D50, ColorSpace.ILLUMINANT_D60 },
+                new Object[] { ColorSpace.Adaptation.BRADFORD, null, ColorSpace.ILLUMINANT_D60 },
+                new Object[] { ColorSpace.Adaptation.BRADFORD, ColorSpace.ILLUMINANT_D60, null },
+        };
+    }
+
+    @Test(expected = NullPointerException.class)
+    @Parameters(method = "chromaticAdaptationNullParameters")
+    public void testChromaticAdaptationNullParameters(ColorSpace.Adaptation adaptation,
+            float[] srcWhitePoint, float[] dstWhitePoint) {
+        ColorSpace.chromaticAdaptation(adaptation, srcWhitePoint, dstWhitePoint);
+    }
+
+    private static Object[] chromaticAdaptationWrongSizedArrays() {
+        return new Object[] {
+                new Object[] { ColorSpace.Adaptation.BRADFORD, new float[] { 1.0f },
+                        ColorSpace.ILLUMINANT_D60 },
+                new Object[] { ColorSpace.Adaptation.BRADFORD, ColorSpace.ILLUMINANT_D60,
+                        new float[] { 1.0f, 1.0f, 1.0f, 1.0f }},
+        };
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    @Parameters(method = "chromaticAdaptationWrongSizedArrays")
+    public void testChromaticAdaptationWrongSizedArrays(ColorSpace.Adaptation adaptation,
+            float[] srcWhitePoint, float[] dstWhitePoint) {
+        ColorSpace.chromaticAdaptation(adaptation, srcWhitePoint, dstWhitePoint);
+    }
+
+    private static float[] sIdentityMatrix = new float[] {
+            1.0f, 0.0f, 0.0f,
+            0.0f, 1.0f, 0.0f,
+            0.0f, 0.0f, 1.0f
+    };
+
+    @Test
+    public void testChromaticAdaptation() {
+        for (ColorSpace.Adaptation adaptation : ColorSpace.Adaptation.values()) {
+            float[][] whitePoints = {
+                    ColorSpace.ILLUMINANT_A,
+                    ColorSpace.ILLUMINANT_B,
+                    ColorSpace.ILLUMINANT_C,
+                    ColorSpace.ILLUMINANT_D50,
+                    ColorSpace.ILLUMINANT_D55,
+                    ColorSpace.ILLUMINANT_D60,
+                    ColorSpace.ILLUMINANT_D65,
+                    ColorSpace.ILLUMINANT_D75,
+                    ColorSpace.ILLUMINANT_E,
+            };
+            for (float[] srcWhitePoint : whitePoints) {
+                for (float[] dstWhitePoint : whitePoints) {
+                    float[] result = ColorSpace.chromaticAdaptation(adaptation, srcWhitePoint,
+                            dstWhitePoint);
+                    assertNotNull(result);
+                    assertEquals(9, result.length);
+                    if (Arrays.equals(srcWhitePoint, dstWhitePoint)) {
+                        assertArrayEquals(sIdentityMatrix, result, 0f);
+                    }
+                }
+            }
+        }
+    }
 
     @SuppressWarnings("SameParameterValue")
-    private static void assertArrayNotEquals(float[] a, float[] b, float eps) {
+    private void assertArrayNotEquals(float[] a, float[] b, float eps) {
         for (int i = 0; i < a.length; i++) {
             if (Float.compare(a[i], b[i]) == 0 || Math.abs(a[i] - b[i]) < eps) {
                 fail("Expected " + a[i] + ", received " + b[i]);
@@ -800,7 +947,7 @@
         }
     }
 
-    private static void assertArrayEquals(float[] a, float[] b, float eps) {
+    private void assertArrayEquals(float[] a, float[] b, float eps) {
         for (int i = 0; i < a.length; i++) {
             if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > eps) {
                 fail("Expected " + a[i] + ", received " + b[i]);
diff --git a/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java b/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java
index cbb485d..5377afb 100644
--- a/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java
+++ b/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java
@@ -29,6 +29,7 @@
 import android.opengl.EGLSurface;
 import android.opengl.EGLSync;
 import android.opengl.GLES20;
+import android.os.SystemProperties;
 
 import androidx.test.filters.SmallTest;
 
@@ -331,6 +332,12 @@
             return;
         }
 
+        // Required functionality for devices released with Android R (11),
+        // skip if launched with older version of Android.
+        if (SystemProperties.getInt("ro.product.first_api_level", 0) < 30) {
+            return;
+        }
+
         mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig, EGL14.EGL_NO_CONTEXT,
                 new int[] { EGL15.EGL_CONTEXT_OPENGL_DEBUG, EGL14.EGL_FALSE, EGL14.EGL_NONE }, 0);
         if (mEglContext == EGL15.EGL_NO_CONTEXT) {
diff --git a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
index 867f8a3..facd7dc 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
@@ -65,6 +65,9 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.function.IntFunction;
 import java.util.function.Supplier;
@@ -211,6 +214,7 @@
     private interface SourceCreator extends IntFunction<ImageDecoder.Source> {};
 
     private SourceCreator[] mCreators = new SourceCreator[] {
+            resId -> ImageDecoder.createSource(getAsByteArray(resId)),
             resId -> ImageDecoder.createSource(getAsByteBufferWrap(resId)),
             resId -> ImageDecoder.createSource(getAsDirectByteBuffer(resId)),
             resId -> ImageDecoder.createSource(getAsReadOnlyByteBuffer(resId)),
@@ -357,9 +361,25 @@
         }
     }
 
+    private Collection<Object[]> paramsForTestSetAllocatorDecodeBitmap() {
+        boolean[] trueFalse = new boolean[] { true, false };
+        List<Object[]> temp = new ArrayList<>();
+        for (Object record : getRecords()) {
+            for (int allocator : ALLOCATORS) {
+                for (boolean doCrop : trueFalse) {
+                    for (boolean doScale : trueFalse) {
+                        temp.add(new Object[]{record, allocator, doCrop, doScale});
+                    }
+                }
+            }
+        }
+        return temp;
+    }
+
     @Test
-    @Parameters(method = "getRecords")
-    public void testSetAllocatorDecodeBitmap(Record record) {
+    @Parameters(method = "paramsForTestSetAllocatorDecodeBitmap")
+    public void testSetAllocatorDecodeBitmap(Record record, int allocator, boolean doCrop,
+                                             boolean doScale) {
         class Listener implements ImageDecoder.OnHeaderDecodedListener {
             public int allocator;
             public boolean doCrop;
@@ -379,51 +399,46 @@
         };
         Listener l = new Listener();
 
-        boolean trueFalse[] = new boolean[] { true, false };
         Resources res = getResources();
         ImageDecoder.Source src = ImageDecoder.createSource(res, record.resId);
         assertNotNull(src);
-        for (int allocator : ALLOCATORS) {
-            for (boolean doCrop : trueFalse) {
-                for (boolean doScale : trueFalse) {
-                    l.doCrop = doCrop;
-                    l.doScale = doScale;
-                    l.allocator = allocator;
+        l.doCrop = doCrop;
+        l.doScale = doScale;
+        l.allocator = allocator;
 
-                    Bitmap bm = null;
-                    try {
-                        bm = ImageDecoder.decodeBitmap(src, l);
-                    } catch (IOException e) {
-                        fail("Failed " + Utils.getAsResourceUri(record.resId)
-                                + " with exception " + e);
-                    }
-                    assertNotNull(bm);
+        Bitmap bm = null;
+        try {
+            bm = ImageDecoder.decodeBitmap(src, l);
+        } catch (IOException e) {
+            fail("Failed " + Utils.getAsResourceUri(record.resId)
+                    + " with exception " + e);
+        }
+        assertNotNull(bm);
 
-                    switch (allocator) {
-                        case ImageDecoder.ALLOCATOR_SOFTWARE:
-                        // TODO: Once Bitmap provides access to its
-                        // SharedMemory, confirm that ALLOCATOR_SHARED_MEMORY
-                        // worked.
-                        case ImageDecoder.ALLOCATOR_SHARED_MEMORY:
-                            assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+        switch (allocator) {
+            case ImageDecoder.ALLOCATOR_SHARED_MEMORY:
+                // For a Bitmap backed by shared memory, asShared will return
+                // the same Bitmap.
+                assertSame(bm, bm.asShared());
 
-                            if (!doScale && !doCrop) {
-                                BitmapFactory.Options options = new BitmapFactory.Options();
-                                options.inScaled = false;
-                                Bitmap reference = BitmapFactory.decodeResource(res,
-                                        record.resId, options);
-                                assertNotNull(reference);
-                                assertTrue(BitmapUtils.compareBitmaps(bm, reference));
-                            }
-                            break;
-                        default:
-                            String name = Utils.getAsResourceUri(record.resId).toString();
-                            assertEquals("image " + name + "; allocator: " + allocator,
-                                         Bitmap.Config.HARDWARE, bm.getConfig());
-                            break;
-                    }
+                // fallthrough
+            case ImageDecoder.ALLOCATOR_SOFTWARE:
+                assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+
+                if (!doScale && !doCrop) {
+                    BitmapFactory.Options options = new BitmapFactory.Options();
+                    options.inScaled = false;
+                    Bitmap reference = BitmapFactory.decodeResource(res,
+                            record.resId, options);
+                    assertNotNull(reference);
+                    assertTrue(BitmapUtils.compareBitmaps(bm, reference));
                 }
-            }
+                break;
+            default:
+                String name = Utils.getAsResourceUri(record.resId).toString();
+                assertEquals("image " + name + "; allocator: " + allocator,
+                             Bitmap.Config.HARDWARE, bm.getConfig());
+                break;
         }
     }
 
@@ -1997,11 +2012,55 @@
         }
     }
 
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testArrayOutOfBounds() {
+        byte[] array = new byte[10];
+        ImageDecoder.createSource(array, 1, 10);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testOffsetOutOfBounds() {
+        byte[] array = new byte[10];
+        ImageDecoder.createSource(array, 10, 0);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testLengthOutOfBounds() {
+        byte[] array = new byte[10];
+        ImageDecoder.createSource(array, 0, 11);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testNegativeLength() {
+        byte[] array = new byte[10];
+        ImageDecoder.createSource(array, 0, -1);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testNegativeOffset() {
+        byte[] array = new byte[10];
+        ImageDecoder.createSource(array, -1, 10);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullByteArray() {
+        ImageDecoder.createSource(null, 0, 0);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullByteArray2() {
+        byte[] array = null;
+        ImageDecoder.createSource(array);
+    }
+
+    @Test(expected = IOException.class)
+    public void testZeroLengthByteArray() throws IOException {
+        ImageDecoder.decodeDrawable(ImageDecoder.createSource(new byte[10], 0, 0));
+    }
+
     @Test(expected = IOException.class)
     public void testZeroLengthByteBuffer() throws IOException {
-        Drawable drawable = ImageDecoder.decodeDrawable(
-            ImageDecoder.createSource(ByteBuffer.wrap(new byte[10], 0, 0)));
-        fail("should not have reached here!");
+        ImageDecoder.decodeDrawable(ImageDecoder.createSource(ByteBuffer.wrap(new byte[10], 0, 0)));
     }
 
     private interface ByteBufferSupplier extends Supplier<ByteBuffer> {};
@@ -2094,6 +2153,24 @@
 
     @Test
     @Parameters(method = "getRecords")
+    public void testOffsetByteArray2(Record record) throws IOException {
+        ImageDecoder.Source src = ImageDecoder.createSource(getAsByteArray(record.resId));
+        Bitmap expected = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+            decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+        });
+
+        final int offset = 10;
+        final int extra = 15;
+        final byte[] array = getAsByteArray(record.resId, offset, extra);
+        src = ImageDecoder.createSource(array, offset, array.length - (offset + extra));
+        Bitmap actual = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+            decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+        });
+        assertTrue(actual.sameAs(expected));
+    }
+
+    @Test
+    @Parameters(method = "getRecords")
     public void testResourceSource(Record record) {
         ImageDecoder.Source src = ImageDecoder.createSource(getResources(), record.resId);
         try {
diff --git a/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java b/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
index d67411f..07bc33e 100644
--- a/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
@@ -60,6 +60,180 @@
     }
 
     @Test
+    public void testIdentityMatrix() {
+        assertNotNull(Matrix.IDENTITY_MATRIX);
+        assertTrue(Matrix.IDENTITY_MATRIX.isIdentity());
+        assertTrue(Matrix.IDENTITY_MATRIX.isAffine());
+        assertTrue(Matrix.IDENTITY_MATRIX.rectStaysRect());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSet() {
+        Matrix m = new Matrix();
+        m.setRotate(90);
+        Matrix.IDENTITY_MATRIX.set(m);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixReset() {
+        Matrix.IDENTITY_MATRIX.reset();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetTranslate() {
+        Matrix.IDENTITY_MATRIX.setTranslate(1f, 1f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetScale() {
+        Matrix.IDENTITY_MATRIX.setScale(.5f, .5f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetScalePivot() {
+        Matrix.IDENTITY_MATRIX.setScale(.5f, .5f, 10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetRotate() {
+        Matrix.IDENTITY_MATRIX.setRotate(60);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetRotateAbout() {
+        Matrix.IDENTITY_MATRIX.setRotate(60, 100f, 100f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetSinCos() {
+        Matrix.IDENTITY_MATRIX.setSinCos(1f, 2f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetSinCosPivot() {
+        Matrix.IDENTITY_MATRIX.setSinCos(1f, 2f, 3f, 4f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetSkew() {
+        Matrix.IDENTITY_MATRIX.setSkew(1f, 2f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetSkewPivot() {
+        Matrix.IDENTITY_MATRIX.setSkew(1f, 2f, 3f, 4f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetConcat() {
+        Matrix a = new Matrix();
+        Matrix b = new Matrix();
+        Matrix.IDENTITY_MATRIX.setConcat(a, b);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreTranslate() {
+        Matrix.IDENTITY_MATRIX.preTranslate(10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreScale() {
+        Matrix.IDENTITY_MATRIX.preScale(10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreScalePivot() {
+        Matrix.IDENTITY_MATRIX.preScale(10f, 10f, 100f, 100f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreRotate() {
+        Matrix.IDENTITY_MATRIX.preRotate(10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreRotatePivot() {
+        Matrix.IDENTITY_MATRIX.preRotate(10f, 10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreSkew() {
+        Matrix.IDENTITY_MATRIX.preSkew(1f, 3f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreSkewPivot() {
+        Matrix.IDENTITY_MATRIX.preSkew(1f, 3f, 4f, 7f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreConcat() {
+        Matrix a = new Matrix();
+        Matrix.IDENTITY_MATRIX.preConcat(a);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostTranslate() {
+        Matrix.IDENTITY_MATRIX.postTranslate(10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostScale() {
+        Matrix.IDENTITY_MATRIX.postScale(10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostScalePivot() {
+        Matrix.IDENTITY_MATRIX.postScale(10f, 10f, 100f, 100f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostRotate() {
+        Matrix.IDENTITY_MATRIX.postRotate(10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostRotatePivot() {
+        Matrix.IDENTITY_MATRIX.postRotate(10f, 10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostSkew() {
+        Matrix.IDENTITY_MATRIX.postSkew(1f, 3f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostSkewPivot() {
+        Matrix.IDENTITY_MATRIX.postSkew(1f, 3f, 4f, 7f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostConcat() {
+        Matrix a = new Matrix();
+        Matrix.IDENTITY_MATRIX.postConcat(a);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetRectToRect() {
+        Matrix.IDENTITY_MATRIX.setRectToRect(new RectF(), new RectF(), ScaleToFit.CENTER);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetPolyToPoly() {
+        float[] src = new float[9];
+        src[0] = 100f;
+        float[] dst = new float[9];
+        dst[0] = 200f;
+        dst[1] = 300f;
+        Matrix.IDENTITY_MATRIX.setPolyToPoly(src, 0, dst, 0, 1);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetValues() {
+        Matrix.IDENTITY_MATRIX.setValues(new float[9]);
+    }
+
+    @Test
     public void testRectStaysRect() {
         assertTrue(mMatrix.rectStaysRect());
         mMatrix.postRotate(80);
diff --git a/tests/tests/graphics/src/android/graphics/cts/Paint_TextBoundsTest.java b/tests/tests/graphics/src/android/graphics/cts/Paint_TextBoundsTest.java
new file mode 100644
index 0000000..708e6a1
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/Paint_TextBoundsTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.graphics.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.AssetManager;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class Paint_TextBoundsTest {
+
+    /**
+     * A character that has 1em x 1em size from (0, 0) origin.
+     */
+    private static final String CHAR_1EMx1EM = "a";
+
+    /**
+     * A character that has 2em x 2em size from (0, 0) origin.
+     */
+    private static final String CHAR_2EMx2EM = "b";
+
+    /**
+     * A character that has 3em x 3em size from (0, 0) origin.
+     */
+    private static final String CHAR_3EMx3EM = "c";
+
+    /**
+     * A character that has 2em x 2em size from (1em, 0) origin.
+     */
+    private static final String CHAR_2EMx2EM_LSB_1EM = "d";
+
+    /**
+     * A character that has 1em x 1em size from (0, 1em) origin.
+     */
+    private static final String CHAR_1EMx1EM_Y1EM_ORIGIN = "e";
+
+    private static Paint getPaint() {
+        Paint paint = new Paint();
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        Typeface typeface = Typeface.createFromAsset(am, "fonts/measurement/bbox.ttf");
+        paint.setTypeface(typeface);
+        paint.setTextSize(100f);  // Make 1em = 100px
+        return paint;
+    }
+
+    @Test
+    public void testSingleChar_1em() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_1EMx1EM, 0, 1, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-100);
+        assertThat(r.right).isEqualTo(100);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testSingleChar_2em() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_2EMx2EM, 0, 1, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-200);
+        assertThat(r.right).isEqualTo(200);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testSingleChar_2em_with_lsb() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_2EMx2EM_LSB_1EM, 0, 1, r);
+        assertThat(r.left).isEqualTo(100);
+        assertThat(r.top).isEqualTo(-200);
+        assertThat(r.right).isEqualTo(300);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testSingleChar_1em_with_y1em_origin() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_1EMx1EM_Y1EM_ORIGIN, 0, 1, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-200);
+        assertThat(r.right).isEqualTo(100);
+        assertThat(r.bottom).isEqualTo(-100);
+    }
+
+    @Test
+    public void testMultiChar_1em_1em() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_1EMx1EM + CHAR_1EMx1EM, 0, 2, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-100);
+        assertThat(r.right).isEqualTo(200);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testMultiChar_1em_2em() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_1EMx1EM + CHAR_2EMx2EM, 0, 2, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-200);
+        assertThat(r.right).isEqualTo(300);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testMultiChar_3em_2em_with_lsb() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_3EMx3EM + CHAR_2EMx2EM_LSB_1EM, 0, 2, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-300);
+        assertThat(r.right).isEqualTo(600);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testMultiChar_1em_with_y1em() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_1EMx1EM + CHAR_1EMx1EM_Y1EM_ORIGIN, 0, 2, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-200);
+        assertThat(r.right).isEqualTo(200);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testMultiChar_1em_5times() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        StringBuilder b = new StringBuilder();
+        for (int i = 0; i < 5; ++i) {
+            b.append(CHAR_1EMx1EM);
+        }
+        p.getTextBounds(b.toString(), 0, b.length(), r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-100);
+        assertThat(r.right).isEqualTo(500);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java
new file mode 100644
index 0000000..3d4f6b6
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertThrows;
+
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.ParcelableColorSpace;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class ParcelableColorSpaceTest {
+
+    public Object[] getNamedColorSpaces() {
+        ColorSpace.Named[] names = ColorSpace.Named.values();
+        Object[] colorSpaces = new Object[names.length];
+        for (int i = 0; i < names.length; i++) {
+            colorSpaces[i] = ColorSpace.get(names[i]);
+        }
+        return colorSpaces;
+    }
+
+    @Test
+    @Parameters(method = "getNamedColorSpaces")
+    public void testNamedReadWrite(ColorSpace colorSpace) {
+        Parcel parcel = Parcel.obtain();
+        try {
+            ParcelableColorSpace inParcelable = new ParcelableColorSpace(colorSpace);
+            parcel.writeParcelable(inParcelable, 0);
+            parcel.setDataPosition(0);
+            ParcelableColorSpace outParcelable = parcel.readParcelable(
+                    ParcelableColorSpace.class.getClassLoader());
+            assertNotNull(outParcelable);
+            assertEquals(inParcelable, outParcelable);
+            assertEquals(inParcelable.getColorSpace(), outParcelable.getColorSpace());
+            // Because these are named, they should all be the same instances
+            assertSame(colorSpace, outParcelable.getColorSpace());
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Test
+    public void testReadWriteCustom() {
+        float[] xyz = new float[] {1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f};
+        ColorSpace colorSpace = new ColorSpace.Rgb("DemoSpace", xyz, 1.9);
+        Parcel parcel = Parcel.obtain();
+        try {
+            ParcelableColorSpace inParcelable = new ParcelableColorSpace(colorSpace);
+            parcel.writeParcelable(inParcelable, 0);
+            parcel.setDataPosition(0);
+            ParcelableColorSpace outParcelable = parcel.readParcelable(
+                    ParcelableColorSpace.class.getClassLoader());
+            assertNotNull(outParcelable);
+            assertEquals(inParcelable, outParcelable);
+            assertEquals(inParcelable.getColorSpace(), outParcelable.getColorSpace());
+            assertSame(colorSpace, inParcelable.getColorSpace());
+            assertNotSame(colorSpace, outParcelable.getColorSpace());
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Test
+    public void testWriteInvalid() {
+        float[] xyz = new float[] {1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f};
+        ColorSpace colorSpace = new ColorSpace.Rgb("DemoSpace", xyz,
+                x -> x, y -> y * 2);
+        assertThrows(IllegalArgumentException.class, () -> {
+            ParcelableColorSpace inParcelable = new ParcelableColorSpace(colorSpace);
+        });
+    }
+
+    @Test
+    @Parameters(method = "getNamedColorSpaces")
+    public void testIsParcelableNamed(ColorSpace colorSpace) {
+        assertTrue(ParcelableColorSpace.isParcelable(colorSpace));
+        // Just make sure the constructor doesn't throw
+        assertEquals(colorSpace, new ParcelableColorSpace(colorSpace).getColorSpace());
+    }
+
+    @Test
+    public void testIsParceableCustom() {
+        float[] xyz = new float[] {1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f};
+        ColorSpace colorSpace = new ColorSpace.Rgb("DemoSpace", xyz, 1.9);
+        assertTrue(ParcelableColorSpace.isParcelable(colorSpace));
+        // Just make sure the constructor doesn't throw
+        assertEquals(colorSpace, new ParcelableColorSpace(colorSpace).getColorSpace());
+    }
+
+    @Test
+    public void testIsParcelableInvalid() {
+        float[] xyz = new float[] {1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f};
+        ColorSpace colorSpace = new ColorSpace.Rgb("DemoSpace", xyz,
+                x -> x, y -> y * 2);
+        assertFalse(ParcelableColorSpace.isParcelable(colorSpace));
+    }
+
+    @Test
+    public void testIsColorSpace() {
+        ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.BT2020);
+        ParcelableColorSpace parcelableColorSpace = new ParcelableColorSpace(colorSpace);
+        Bitmap bitmap = Bitmap.createBitmap(10, 10,
+                Bitmap.Config.RGBA_F16, false, parcelableColorSpace);
+        assertNotNull(bitmap);
+        ColorSpace bitmapColorSpace = bitmap.getColorSpace();
+        assertNotNull(bitmapColorSpace);
+        assertEquals(colorSpace.getId(), bitmapColorSpace.getId());
+        assertEquals(parcelableColorSpace.getColorSpace(), bitmapColorSpace);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/Shader_TileModeTest.java b/tests/tests/graphics/src/android/graphics/cts/Shader_TileModeTest.java
index 455f59e..b7c7a18 100644
--- a/tests/tests/graphics/src/android/graphics/cts/Shader_TileModeTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/Shader_TileModeTest.java
@@ -34,14 +34,16 @@
         assertEquals(TileMode.CLAMP, TileMode.valueOf("CLAMP"));
         assertEquals(TileMode.MIRROR, TileMode.valueOf("MIRROR"));
         assertEquals(TileMode.REPEAT, TileMode.valueOf("REPEAT"));
+        assertEquals(TileMode.DECAL, TileMode.valueOf("DECAL"));
     }
 
     @Test
     public void testValues() {
         TileMode[] tileMode = TileMode.values();
-        assertEquals(3, tileMode.length);
+        assertEquals(4, tileMode.length);
         assertEquals(TileMode.CLAMP, tileMode[0]);
         assertEquals(TileMode.REPEAT, tileMode[1]);
         assertEquals(TileMode.MIRROR, tileMode[2]);
+        assertEquals(TileMode.DECAL, tileMode[3]);
     }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java
index 14d3000..d461535 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java
@@ -143,15 +143,11 @@
         return mContext.getPackageManager().hasSystemFeature(requiredFeature);
     }
 
-    private static Bitmap takeScreenshot() {
+    private static Bitmap takeScreenshot(int width, int height) {
         assertNotNull("sActivity should not be null", sActivity);
-        Rect srcRect = new Rect();
-        sActivity.findViewById(R.id.surfaceview).getGlobalVisibleRect(srcRect);
         SynchronousPixelCopy copy = new SynchronousPixelCopy();
-        Bitmap dest =
-                Bitmap.createBitmap(srcRect.width(), srcRect.height(), Bitmap.Config.ARGB_8888);
-        int copyResult =
-                copy.request((SurfaceView) sActivity.findViewById(R.id.surfaceview), srcRect, dest);
+        Bitmap dest = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        int copyResult = copy.request((SurfaceView) sActivity.findViewById(R.id.surfaceview), dest);
         assertEquals("PixelCopy failed", PixelCopy.SUCCESS, copyResult);
         return dest;
     }
@@ -168,11 +164,9 @@
     }
 
     private static boolean validatePixelValuesAfterRotation(
-            boolean setPreTransform, int preTransformHint) {
-        Bitmap bitmap = takeScreenshot();
+            int width, int height, boolean setPreTransform, int preTransformHint) {
+        Bitmap bitmap = takeScreenshot(width, height);
 
-        int width = bitmap.getWidth();
-        int height = bitmap.getHeight();
         int diff = 0;
         if (!setPreTransform || preTransformHint == 0x1 /*VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR*/) {
             diff += pixelDiff(bitmap.getPixel(0, 0), 255, 0, 0);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java
index ec44400..c0ef374 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java
@@ -265,9 +265,6 @@
 
         for (int x = rangeRect.left; x < rangeRect.right; x++) {
             for (int y = rangeRect.top; y < rangeRect.bottom; y++) {
-                if (image1.getPixel(x, y) != image2.getPixel(x, y)) {
-                    return false;
-                }
                 int color1 = image1.getPixel(x, y);
                 int color2 = image2.getPixel(x, y);
                 int rDiff = Math.abs(Color.red(color1) - Color.red(color2));
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
index 60f7b0a..a371932 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
@@ -559,4 +559,19 @@
             resources.getDrawable(R.drawable.testimage).setAlpha(restoreAlpha);
         }
     }
+
+    @Test
+    public void testSetBitmap() {
+        Resources resources = mContext.getResources();
+        Bitmap source = BitmapFactory.decodeResource(resources, R.raw.testimage);
+        BitmapDrawable bitmapDrawable = new BitmapDrawable(resources, source);
+        assertSame(source, bitmapDrawable.getBitmap());
+
+        Bitmap bm = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        bitmapDrawable.setBitmap(bm);
+        assertSame(bm, bitmapDrawable.getBitmap());
+
+        bitmapDrawable.setBitmap(null);
+        assertNull(bitmapDrawable.getBitmap());
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontTest.java b/tests/tests/graphics/src/android/graphics/fonts/FontTest.java
index 951cea6..db88023 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/FontTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontTest.java
@@ -26,6 +26,9 @@
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Typeface;
 import android.graphics.cts.R;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
@@ -1000,4 +1003,104 @@
         final Resources res = InstrumentationRegistry.getTargetContext().getResources();
         new Font.Builder(res, R.font.ascii).setWeight(FontStyle.FONT_WEIGHT_MIN - 1).build();
     }
+
+    @Test
+    public void builder_with_font_with_axis() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        // WeightEqualsEmVariableFont adjust glyph advance as follows
+        //  glyph advance = 'wght' value / 1000
+        // Thus, by setting text size to 1000px, the glyph advance will equals to passed wght value.
+        Font baseFont = new Font.Builder(assets, "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
+                .build();
+
+        FontStyle style = new FontStyle(123, FontStyle.FONT_SLANT_ITALIC);
+
+        for (int weight = 50; weight < 1000; weight += 50) {
+            Font clonedFont = new Font.Builder(baseFont)
+                    .setWeight(style.getWeight())
+                    .setSlant(style.getSlant())
+                    .setFontVariationSettings("'wght' " + weight)
+                    .build();
+
+            // New font should have the same style passed.
+            assertEquals(style.getWeight(), clonedFont.getStyle().getWeight());
+            assertEquals(style.getSlant(), clonedFont.getStyle().getSlant());
+
+            Paint p = new Paint();
+            p.setTextSize(1000);  // make 1em = 1000px = weight
+            p.setTypeface(new Typeface.CustomFallbackBuilder(
+                    new FontFamily.Builder(clonedFont).build()
+            ).build());
+            assertEquals(weight, p.measureText("a"), 0);
+
+        }
+    }
+
+    @Test
+    public void builder_with_explicit_style() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        Font baseFont = new Font.Builder(assets, "fonts/others/samplefont.ttf").build();
+        FontStyle style = new FontStyle(123, FontStyle.FONT_SLANT_ITALIC);
+        Font clonedFont = new Font.Builder(baseFont)
+                .setWeight(style.getWeight())
+                .setSlant(style.getSlant())
+                .build();
+
+        assertEquals(style.getWeight(), clonedFont.getStyle().getWeight());
+        assertEquals(style.getSlant(), clonedFont.getStyle().getSlant());
+    }
+
+    @Test
+    public void builder_style_resolve_default() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        Font baseFont = new Font.Builder(assets,
+                "fonts/family_selection/ttf/ascii_l3em_weight600_italic.ttf").build();
+        Font clonedFont = new Font.Builder(baseFont).build();
+
+        assertEquals(600, clonedFont.getStyle().getWeight());
+        assertEquals(FontStyle.FONT_SLANT_ITALIC, clonedFont.getStyle().getSlant());
+    }
+
+    @Test
+    public void getBoundingBox() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        Font font = new Font.Builder(assets, "fonts/measurement/a3em.ttf").build();
+        Paint paint = new Paint();
+        paint.setTextSize(100);  // make 1em = 100px
+
+        int glyphID = 1;  // See a3em.ttx file for the Glyph ID.
+
+        RectF rect = new RectF();
+        float advance = font.getGlyphBounds(glyphID, paint, rect);
+
+        assertEquals(100f, advance, 0f);
+        // Glyph bbox is 0.1em shifted to right. See lsb value in hmtx in ttx file.
+        assertEquals(rect.left, 10f, 0f);
+        assertEquals(rect.top, -100f, 0f);
+        assertEquals(rect.right, 110f, 0f);
+        assertEquals(rect.bottom, 0f, 0f);
+    }
+
+    @Test
+    public void getFontMetrics() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        Font font = new Font.Builder(assets, "fonts/measurement/a3em.ttf").build();
+        Paint paint = new Paint();
+        paint.setTextSize(100);  // make 1em = 100px
+
+        Paint.FontMetrics metrics = new Paint.FontMetrics();
+        font.getMetrics(paint, metrics);
+
+        assertEquals(-100f, metrics.ascent, 0f);
+        assertEquals(20f, metrics.descent, 0f);
+        // This refers head.yMax which is not explicitly visible in ttx file.
+        assertEquals(-300f, metrics.top, 0f);
+        // This refers head.yMin which is not explicitly visible in ttx file.
+        assertEquals(0f, metrics.bottom, 0f);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/GlyphStyleTest.java b/tests/tests/graphics/src/android/graphics/text/cts/GlyphStyleTest.java
new file mode 100644
index 0000000..c6ce899
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/text/cts/GlyphStyleTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.text.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.text.GlyphStyle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GlyphStyleTest {
+    private final Paint mPaint;
+
+    public GlyphStyleTest() {
+        mPaint = new Paint();
+        mPaint.setColor(Color.BLUE);
+        mPaint.setTextSize(123f);
+        mPaint.setTextSkewX(0.5f);
+        mPaint.setTextScaleX(0.6f);
+        mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+    }
+
+    @Test
+    public void setAndGet() {
+        GlyphStyle style = new GlyphStyle(Color.RED, 0f, 0f, 0f, 0);
+
+        style.setColor(Color.BLACK);
+        assertThat(style.getColor()).isEqualTo(Color.BLACK);
+
+        style.setFontSize(123f);
+        assertThat(style.getFontSize()).isEqualTo(123f);
+
+        style.setScaleX(0.5f);
+        assertThat(style.getScaleX()).isEqualTo(0.5f);
+
+        style.setSkewX(0.5f);
+        assertThat(style.getSkewX()).isEqualTo(0.5f);
+
+        style.setFlags(Paint.LINEAR_TEXT_FLAG);
+        assertThat(style.getFlags()).isEqualTo(Paint.LINEAR_TEXT_FLAG);
+    }
+
+    @Test
+    public void createFromPaint() {
+        GlyphStyle style = new GlyphStyle(mPaint);
+
+        assertThat(style.getColor()).isEqualTo(mPaint.getColor());
+        assertThat(style.getFontSize()).isEqualTo(mPaint.getTextSize());
+        assertThat(style.getScaleX()).isEqualTo(mPaint.getTextScaleX());
+        assertThat(style.getSkewX()).isEqualTo(mPaint.getTextSkewX());
+        assertThat(style.getFlags()).isEqualTo(mPaint.getFlags());
+    }
+
+    @Test
+    public void setFromPaint() {
+        GlyphStyle style = new GlyphStyle(Color.RED, 0f, 0f, 0f, 0);
+        style.setFromPaint(mPaint);
+
+        assertThat(style.getColor()).isEqualTo(mPaint.getColor());
+        assertThat(style.getFontSize()).isEqualTo(mPaint.getTextSize());
+        assertThat(style.getScaleX()).isEqualTo(mPaint.getTextScaleX());
+        assertThat(style.getSkewX()).isEqualTo(mPaint.getTextSkewX());
+        assertThat(style.getFlags()).isEqualTo(mPaint.getFlags());
+    }
+
+    @Test
+    public void applyToPaint() {
+        GlyphStyle style = new GlyphStyle(Color.RED, 321f, 0.4f, 0.3f, Paint.LINEAR_TEXT_FLAG);
+
+        Paint paint = new Paint();
+        style.applyToPaint(paint);
+
+        assertThat(paint.getColor()).isEqualTo(Color.RED);
+        assertThat(paint.getTextSize()).isEqualTo(style.getFontSize());
+        assertThat(paint.getTextSkewX()).isEqualTo(style.getSkewX());
+        assertThat(paint.getTextScaleX()).isEqualTo(style.getScaleX());
+        assertThat(paint.getFlags()).isEqualTo(style.getFlags());
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java b/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
index 096f366..52a2a74 100644
--- a/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
+++ b/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
@@ -187,6 +187,31 @@
         assertEquals(twoCharRect, out);
     }
 
+    @Test
+    public void testGetBounds_RTL() {
+        Paint paint = new Paint();
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        Typeface typeface = Typeface.createFromAsset(am, "fonts/measurement/bbox.ttf");
+        paint.setTypeface(typeface);
+        paint.setTextSize(100f);  // Make 1em = 100px
+        String text = "\u0028";  // U+0028 is 1em x 1em in LTR and 3em x 3em in RTL.
+        Rect ltrRect = new Rect();
+        Rect rtlRect = new Rect();
+
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(paint, text.length(), false)
+                .build()
+                .getBounds(0, 1, ltrRect);
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(paint, text.length(), true)
+                .build()
+                .getBounds(0, 1, rtlRect);
+
+
+        assertEquals(new Rect(0, -100, 100, 0), ltrRect);
+        assertEquals(new Rect(0, -300, 300, 0), rtlRect);
+    }
+
     @Test(expected = IllegalArgumentException.class)
     public void testGetBounds_StartSmallerThanZero() {
         String text = "Hello, World";
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/TextShaperTest.java b/tests/tests/graphics/src/android/graphics/text/cts/TextShaperTest.java
new file mode 100644
index 0000000..f714d10
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/text/cts/TextShaperTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.text.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.graphics.fonts.FontVariationAxis;
+import android.graphics.text.GlyphStyle;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextShaper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextShaperTest {
+
+    @Test
+    public void shapeText() {
+        // Setup
+        Paint paint = new Paint();
+        paint.setTextSize(100f);
+        String text = "Hello, World.";
+
+        // Act
+        PositionedGlyphs result = TextShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Assert
+        // Glyph must be included. (the count cannot be expected since there could be ligature).
+        assertThat(result.glyphCount()).isNotEqualTo(0);
+        assertThat(result.getStyle()).isEqualTo(new GlyphStyle(paint));
+        for (int i = 0; i < result.glyphCount(); ++i) {
+            // Glyph ID = 0 is reserved for Tofu, thus expecting all character has glyph.
+            assertThat(result.getGlyphId(i)).isNotEqualTo(0);
+        }
+
+        // Must have horizontal advance.
+        assertThat(result.getTotalAdvance()).isGreaterThan(0f);
+        float ascent = result.getAscent();
+        float descent = result.getDescent();
+        // Usually font has negative ascent value which is relative from the baseline.
+        assertThat(ascent).isLessThan(0f);
+        // Usually font has positive descent value which is relative from the baseline.
+        assertThat(descent).isGreaterThan(0f);
+        Paint.FontMetrics metrics = new Paint.FontMetrics();
+        for (int i = 0; i < result.glyphCount(); ++i) {
+            result.getFont(i).getMetrics(paint, metrics);
+            // The overall ascent must be smaller (wider) than each font ascent.
+            assertThat(ascent <= metrics.ascent).isTrue();
+            // The overall descent must be bigger (wider) than each font descent.
+            assertThat(descent >= metrics.descent).isTrue();
+        }
+    }
+
+    @Test
+    public void shapeText_differentPaint() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+
+        // Act
+        paint.setTextSize(100f);  // Shape text with 100px
+        PositionedGlyphs result1 = TextShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        paint.setTextSize(50f);  // Shape text with 50px
+        PositionedGlyphs result2 = TextShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Assert
+        // The total advance should be different.
+        assertThat(result1.getTotalAdvance()).isNotEqualTo(result2.getTotalAdvance());
+
+        // The size change doesn't affect glyph selection.
+        assertThat(result1.glyphCount()).isEqualTo(result2.glyphCount());
+        for (int i = 0; i < result1.glyphCount(); ++i) {
+            assertThat(result1.getGlyphId(i)).isEqualTo(result2.getGlyphId(i));
+        }
+    }
+
+    @Test
+    public void shapeText_context() {
+        // Setup
+        Paint paint = new Paint();
+        paint.setTextSize(100f);
+
+        // Arabic script change form (glyph) based on position.
+        String text = "\u0645\u0631\u062D\u0628\u0627";
+
+        // Act
+        PositionedGlyphs resultWithContext = TextShaper.shapeTextRun(
+                text, 0, 1, 0, text.length(), 0f, 0f, true, paint);
+        PositionedGlyphs resultWithoutContext = TextShaper.shapeTextRun(
+                text, 0, 1, 0, 1, 0f, 0f, true, paint);
+
+        // Assert
+        assertThat(resultWithContext.getGlyphId(0))
+                .isNotEqualTo(resultWithoutContext.getGlyphId(0));
+    }
+
+    @Test
+    public void shapeText_twoAPISameResult() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+        paint.setTextSize(100f);  // Shape text with 100px
+
+        // Act
+        PositionedGlyphs resultString = TextShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        char[] charArray = text.toCharArray();
+        PositionedGlyphs resultChars = TextShaper.shapeTextRun(
+                charArray, 0, charArray.length, 0, charArray.length, 0f, 0f, false, paint);
+
+        // Asserts
+        assertThat(resultString.glyphCount()).isEqualTo(resultChars.glyphCount());
+        assertThat(resultString.getTotalAdvance()).isEqualTo(resultChars.getTotalAdvance());
+        assertThat(resultString.getAscent()).isEqualTo(resultChars.getAscent());
+        assertThat(resultString.getDescent()).isEqualTo(resultChars.getDescent());
+        assertThat(resultString.getStyle()).isEqualTo(resultChars.getStyle());
+        for (int i = 0; i < resultString.glyphCount(); ++i) {
+            assertThat(resultString.getGlyphId(i)).isEqualTo(resultChars.getGlyphId(i));
+            assertThat(resultString.getFont(i)).isEqualTo(resultChars.getFont(i));
+            assertThat(resultString.getPositionX(i)).isEqualTo(resultChars.getPositionX(i));
+            assertThat(resultString.getPositionY(i)).isEqualTo(resultChars.getPositionY(i));
+        }
+    }
+
+    @Test
+    public void shapeText_multiLanguage() {
+        // Setup
+        Paint paint = new Paint();
+        paint.setTextSize(100f);
+        String text = "Hello, Emoji: \uD83E\uDE90";  // Usually emoji is came from ColorEmoji font.
+
+        // Act
+        PositionedGlyphs result = TextShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Assert
+        HashSet<Font> set = new HashSet<>();
+        for (int i = 0; i < result.glyphCount(); ++i) {
+            set.add(result.getFont(i));
+        }
+        assertThat(set.size()).isEqualTo(2);  // Roboto + Emoji is expected
+    }
+
+    @Test
+    public void shapeText_FontCreateFromNative() throws IOException {
+        // Setup
+        Context ctx = InstrumentationRegistry.getTargetContext();
+        Paint paint = new Paint();
+        Font originalFont = new Font.Builder(
+                ctx.getAssets(),
+                "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
+                .build();
+        Typeface typeface = new Typeface.CustomFallbackBuilder(
+                new FontFamily.Builder(originalFont).build()
+        ).build();
+        paint.setTypeface(typeface);
+        // setFontVariationSettings creates Typeface internally and it is not from Java Font object.
+        paint.setFontVariationSettings("'wght' 250");
+
+        // Act
+        PositionedGlyphs res = TextShaper.shapeTextRun("a", 0, 1, 0, 1, 0f, 0f, false, paint);
+
+        // Assert
+        Font font = res.getFont(0);
+        assertThat(font.getBuffer()).isEqualTo(originalFont.getBuffer());
+        assertThat(font.getTtcIndex()).isEqualTo(originalFont.getTtcIndex());
+        FontVariationAxis[] axes = font.getAxes();
+        assertThat(axes.length).isEqualTo(1);
+        assertThat(axes[0].getTag()).isEqualTo("wght");
+        assertThat(axes[0].getStyleValue()).isEqualTo(250f);
+    }
+
+    @Test
+    public void positionedGlyphs_equality() {
+        // Setup
+        Paint paint = new Paint();
+        paint.setTextSize(100f);
+
+        // Act
+        PositionedGlyphs glyphs = TextShaper.shapeTextRun(
+                "abcde", 0, 5, 0, 5, 0f, 0f, true, paint);
+        PositionedGlyphs eqGlyphs = TextShaper.shapeTextRun(
+                "abcde", 0, 5, 0, 5, 0f, 0f, true, paint);
+        PositionedGlyphs reversedGlyphs = TextShaper.shapeTextRun(
+                "edcba", 0, 5, 0, 5, 0f, 0f, true, paint);
+        PositionedGlyphs substrGlyphs = TextShaper.shapeTextRun(
+                "edcba", 0, 3, 0, 3, 0f, 0f, true, paint);
+        paint.setTextSize(50f);
+        PositionedGlyphs differentStyleGlyphs = TextShaper.shapeTextRun(
+                "edcba", 0, 3, 0, 3, 0f, 0f, true, paint);
+
+        // Assert
+        assertThat(glyphs).isEqualTo(eqGlyphs);
+
+        assertThat(glyphs).isNotEqualTo(reversedGlyphs);
+        assertThat(glyphs).isNotEqualTo(substrGlyphs);
+        assertThat(glyphs).isNotEqualTo(differentStyleGlyphs);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void positionedGlyphs_IllegalArgument_glyphID() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+        paint.setTextSize(100f);  // Shape text with 100px
+        PositionedGlyphs res = TextShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Act
+        res.getGlyphId(res.glyphCount());  // throws IllegalArgumentException
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void resultTest_IllegalArgument_font() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+        paint.setTextSize(100f);  // Shape text with 100px
+        PositionedGlyphs res = TextShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Act
+        res.getFont(res.glyphCount());  // throws IllegalArgumentException
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void resultTest_IllegalArgument_X() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+        paint.setTextSize(100f);  // Shape text with 100px
+        PositionedGlyphs res = TextShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Act
+        res.getPositionX(res.glyphCount());  // throws IllegalArgumentException
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void resultTest_IllegalArgument_Y() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+        paint.setTextSize(100f);  // Shape text with 100px
+        PositionedGlyphs res = TextShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Act
+        res.getPositionY(res.glyphCount());  // throws IllegalArgumentException
+    }
+
+    // TODO(nona): Add pixel comparison tests once we have Canvas.drawGlyph APIs.
+}
diff --git a/tests/tests/hardware/Android.bp b/tests/tests/hardware/Android.bp
index ea60dda..857164d 100644
--- a/tests/tests/hardware/Android.bp
+++ b/tests/tests/hardware/Android.bp
@@ -31,6 +31,7 @@
         "compatibility-device-util-axt",
         "cts-input-lib",
         "ctstestrunner-axt",
+        "cts-wm-util",
         "mockito-target-minus-junit4",
         "platform-test-annotations",
         "ub-uiautomator",
diff --git a/tests/tests/hardware/AndroidManifest.xml b/tests/tests/hardware/AndroidManifest.xml
index 4b56763..78ff2aa 100644
--- a/tests/tests/hardware/AndroidManifest.xml
+++ b/tests/tests/hardware/AndroidManifest.xml
@@ -75,7 +75,8 @@
             android:label="InputCtsActivity"
             android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation
                     |screenLayout|fontScale|uiMode|orientation|density|screenSize
-                    |smallestScreenSize"/>
+                    |smallestScreenSize">
+        </activity>
 
         <activity android:name="android.hardware.cts.FingerprintTestActivity"
             android:label="FingerprintTestActivity">
diff --git a/tests/tests/hardware/res/raw/gamevice_gv186_keyeventtests.json b/tests/tests/hardware/res/raw/gamevice_gv186_keyeventtests.json
new file mode 100644
index 0000000..13fabcd
--- /dev/null
+++ b/tests/tests/hardware/res/raw/gamevice_gv186_keyeventtests.json
@@ -0,0 +1,171 @@
+[
+  {
+    "name": "Press BUTTON_A",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_A"},
+      {"action": "UP", "keycode": "BUTTON_A"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_B",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_B"},
+      {"action": "UP", "keycode": "BUTTON_B"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_X",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_X"},
+      {"action": "UP", "keycode": "BUTTON_X"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_Y",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_Y"},
+      {"action": "UP", "keycode": "BUTTON_Y"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_LB",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L1"},
+      {"action": "UP", "keycode": "BUTTON_L1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_RB",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R1"},
+      {"action": "UP", "keycode": "BUTTON_R1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L2",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L2"},
+      {"action": "UP", "keycode": "BUTTON_L2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R2",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R2"},
+      {"action": "UP", "keycode": "BUTTON_R2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_THUMBL",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+      {"action": "UP", "keycode": "BUTTON_THUMBL"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_THUMBR",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+      {"action": "UP", "keycode": "BUTTON_THUMBR"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_SELECT (left arrow)",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+      {"action": "UP", "keycode": "BUTTON_SELECT"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_START",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_START"},
+      {"action": "UP", "keycode": "BUTTON_START"}
+    ]
+  },
+
+  {
+    "name": "Press bottom MODE button (looks like [|])",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_MODE"},
+      {"action": "UP", "keycode": "BUTTON_MODE"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/gamevice_gv186_motioneventtests.json b/tests/tests/hardware/res/raw/gamevice_gv186_motioneventtests.json
new file mode 100755
index 0000000..81ca51f
--- /dev/null
+++ b/tests/tests/hardware/res/raw/gamevice_gv186_motioneventtests.json
@@ -0,0 +1,216 @@
+[
+  {
+    // This device produces a MOVE with coordinates in generic axes due to the HID usage
+    // mapping of HAT1X/HAT1Y by kernel.
+    "name": "Initial check - Ignore move event.",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {}}
+    ]
+  },
+
+  {
+    "name": "Press left DPAD key",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press right DPAD key",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press up DPAD key",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Press down DPAD key",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press left",
+    "reports": [
+      [0xc0, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press right",
+    "reports": [
+      [0x3f, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x7f, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press up",
+    "reports": [
+      [0x00, 0xc0, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press down",
+    "reports": [
+      [0x00, 0x3f, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x7f, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press left",
+    "reports": [
+      [0x00, 0x00, 0xc0, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press right",
+    "reports": [
+      [0x00, 0x00, 0x3f, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x7f, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press up",
+    "reports": [
+      [0x00, 0x00, 0x00, 0xc0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press down",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x3f, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x7f, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Left trigger - quick press",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.5, "AXIS_BRAKE": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 1.0, "AXIS_BRAKE": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.0, "AXIS_BRAKE": 0.0}}
+    ]
+  },
+
+  {
+    "name": "Right trigger - quick press",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.5, "AXIS_GAS": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 1.0, "AXIS_GAS": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.0, "AXIS_GAS": 0.0}}
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/gamevice_gv186_register.json b/tests/tests/hardware/res/raw/gamevice_gv186_register.json
new file mode 100755
index 0000000..73a6583
--- /dev/null
+++ b/tests/tests/hardware/res/raw/gamevice_gv186_register.json
@@ -0,0 +1,21 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "GAMEVICE Controller for Google Pixel-GV186 (Test)",
+  "vid": 0x27f8,
+  "pid": 0x0bbe,
+  "bus": "bluetooth",
+  "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0xa1, 0x02, 0x15, 0x81, 0x25, 0x7f, 0x05,
+    0x01, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95, 0x04, 0x35, 0x00, 0x46, 0xff, 0x00, 0x09,
+    0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, 0x75, 0x08, 0x95, 0x02, 0x15, 0x01,
+    0x26, 0xff, 0x00, 0x09, 0x39, 0x09, 0x39, 0x81, 0x02, 0xc0, 0x05, 0x07, 0x19, 0x4f, 0x29,
+    0x52, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02, 0x05, 0x01, 0x09, 0x90,
+    0x09, 0x91, 0x09, 0x92, 0x09, 0x93, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02, 0x75, 0x01, 0x95,
+    0x10, 0x05, 0x09, 0x19, 0x01, 0x29, 0x10, 0x81, 0x02, 0x06, 0x02, 0xff, 0x09, 0x01, 0xa1,
+    0x01, 0x15, 0x00, 0x25, 0x01, 0x09, 0x04, 0x75, 0x01, 0x95, 0x01, 0x81, 0x02, 0xc0, 0x05,
+    0x0c, 0x09, 0x01, 0xa1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x0a, 0x24, 0x02, 0x75, 0x01, 0x95,
+    0x01, 0x81, 0x06, 0xc0, 0x75, 0x01, 0x95, 0x06, 0x81, 0x03, 0x15, 0x00, 0x25, 0xff, 0x05,
+    0x02, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95, 0x02, 0x35, 0x00, 0x45, 0xff, 0x09, 0xc4,
+    0x09, 0xc5, 0x81, 0x02, 0xc0, 0x06, 0x00, 0xff, 0x09, 0x80, 0x75, 0x08, 0x95, 0x08, 0x15,
+    0x00, 0x26, 0xff, 0x00, 0xb1, 0x02, 0xc0, 0xc0]
+}
diff --git a/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json b/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json
index cfb7d4b..b6d9680 100644
--- a/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json
+++ b/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json
@@ -92,31 +92,31 @@
     "events": [
       {
         "action": "MOVE",
-        "axes": {"AXIS_X": 1}
+        "axes": {"AXIS_X": 1, "AXIS_RELATIVE_X": 1}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_X": 768}
+        "axes": {"AXIS_X": 768, "AXIS_RELATIVE_X": 768}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_X": -768}
+        "axes": {"AXIS_X": -768, "AXIS_RELATIVE_X": -768}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_Y": 768}
+        "axes": {"AXIS_Y": 768, "AXIS_RELATIVE_Y": 768}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_Y": -768}
+        "axes": {"AXIS_Y": -768, "AXIS_RELATIVE_Y": -768}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_X": 768, "AXIS_Y": 768}
+        "axes": {"AXIS_X": 768, "AXIS_Y": 768, "AXIS_RELATIVE_X": 768, "AXIS_RELATIVE_Y": 768}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_X": -768, "AXIS_Y": -768}
+        "axes": {"AXIS_X": -768, "AXIS_Y": -768, "AXIS_RELATIVE_X": -768, "AXIS_RELATIVE_Y": -768}
       }
     ]
   }
diff --git a/tests/tests/hardware/res/raw/razer_junglecat_keyeventtests.json b/tests/tests/hardware/res/raw/razer_junglecat_keyeventtests.json
new file mode 100644
index 0000000..06af08c
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_junglecat_keyeventtests.json
@@ -0,0 +1,158 @@
+[
+  {
+    "name": "Press BUTTON_A",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_A"},
+      {"action": "UP", "keycode": "BUTTON_A"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_B",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_B"},
+      {"action": "UP", "keycode": "BUTTON_B"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_X",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_X"},
+      {"action": "UP", "keycode": "BUTTON_X"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_Y",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_Y"},
+      {"action": "UP", "keycode": "BUTTON_Y"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L1",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L1"},
+      {"action": "UP", "keycode": "BUTTON_L1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R1",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R1"},
+      {"action": "UP", "keycode": "BUTTON_R1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L2",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x10, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L2"},
+      {"action": "UP", "keycode": "BUTTON_L2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R2",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R2"},
+      {"action": "UP", "keycode": "BUTTON_R2"}
+    ]
+  },
+
+  {
+    "name": "Press Left Stick Thumb Button",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+      {"action": "UP", "keycode": "BUTTON_THUMBL"}
+    ]
+  },
+
+  {
+    "name": "Press Right Stick Thumb Button",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+      {"action": "UP", "keycode": "BUTTON_THUMBR"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_SELECT",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+      {"action": "UP", "keycode": "BUTTON_SELECT"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_START",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_START"},
+      {"action": "UP", "keycode": "BUTTON_START"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_junglecat_motioneventtests.json b/tests/tests/hardware/res/raw/razer_junglecat_motioneventtests.json
new file mode 100644
index 0000000..11f088d
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_junglecat_motioneventtests.json
@@ -0,0 +1,184 @@
+[
+  {
+    "name": "Sanity check - should not produce any events",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+    ]
+  },
+
+  {
+    "name": "Press left DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press right DPAD key",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press up DPAD key",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Press down DPAD key",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press left",
+    "reports": [
+        [0x01, 0x3f, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x00, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": -0.51}},
+      {"action": "MOVE", "axes": {"AXIS_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press right",
+    "reports": [
+        [0x01, 0xc0, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0xff, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press up",
+    "reports": [
+        [0x01, 0x80, 0x3f, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x00, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": -0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press down",
+    "reports": [
+        [0x01, 0x80, 0xc0, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0xff, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press left",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x3f, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x00, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": -0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Z": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press right",
+    "reports": [
+        [0x01, 0x80, 0x80, 0xc0, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0xff, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press up",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x80, 0x3f, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": -0.51}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press down",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x80, 0xc0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_junglecat_register.json b/tests/tests/hardware/res/raw/razer_junglecat_register.json
new file mode 100644
index 0000000..2a7a48f
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_junglecat_register.json
@@ -0,0 +1,21 @@
+{
+    "id": 1,
+    "command": "register",
+    "name": "Razer Junglecat (Bluetooth Test)",
+    "vid": 0x1532,
+    "pid": 0x0709,
+    "bus": "bluetooth",
+    "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09,
+        0x32, 0x09, 0x35, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, 0x09,
+        0x39, 0x15, 0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3b, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95,
+        0x01, 0x81, 0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0f, 0x15, 0x00, 0x25, 0x01,
+        0x75, 0x01, 0x95, 0x0f, 0x81, 0x02, 0x75, 0x0d, 0x95, 0x01, 0x81, 0x03, 0x05, 0x02, 0x09,
+        0xc5, 0x09, 0xc4, 0x15, 0x00, 0x26, 0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08,
+        0x95, 0x02, 0x81, 0x02, 0x06, 0x00, 0xff, 0x09, 0x21, 0x95, 0x02, 0x81, 0x02, 0x85, 0x02,
+        0x0a, 0x21, 0x27, 0x95, 0x2f, 0xb1, 0x02, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85,
+        0x03, 0x0a, 0x23, 0x02, 0x0a, 0x24, 0x02, 0x09, 0x40, 0x09, 0xe9, 0x09, 0xea, 0x09, 0x30,
+        0x09, 0x32, 0x0a, 0xa2, 0x01, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02,
+        0x75, 0x01, 0x95, 0x08, 0x81, 0x03, 0x75, 0x08, 0x95, 0x0a, 0x81, 0x01, 0xc0, 0x05, 0x01,
+        0x09, 0x00, 0xa1, 0x01, 0x85, 0x05, 0x09, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x35, 0x00,
+        0x46, 0xff, 0x00, 0x75, 0x08, 0x95, 0x0c, 0x81, 0x00, 0xc0]
+}
diff --git a/tests/tests/hardware/res/raw/razer_kishi_keyeventtests.json b/tests/tests/hardware/res/raw/razer_kishi_keyeventtests.json
new file mode 100644
index 0000000..a279d5a
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_kishi_keyeventtests.json
@@ -0,0 +1,171 @@
+[
+  {
+    "name": "Press BUTTON_A",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_A"},
+      {"action": "UP", "keycode": "BUTTON_A"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_B",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_B"},
+      {"action": "UP", "keycode": "BUTTON_B"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_X",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_X"},
+      {"action": "UP", "keycode": "BUTTON_X"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_Y",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_Y"},
+      {"action": "UP", "keycode": "BUTTON_Y"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L1",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L1"},
+      {"action": "UP", "keycode": "BUTTON_L1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R1",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R1"},
+      {"action": "UP", "keycode": "BUTTON_R1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L2",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L2"},
+      {"action": "UP", "keycode": "BUTTON_L2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R2",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R2"},
+      {"action": "UP", "keycode": "BUTTON_R2"}
+    ]
+  },
+
+  {
+    "name": "Press Left Stick Thumb Button",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+      {"action": "UP", "keycode": "BUTTON_THUMBL"}
+    ]
+  },
+
+  {
+    "name": "Press Right Stick Thumb Button",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+      {"action": "UP", "keycode": "BUTTON_THUMBR"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_SELECT",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+      {"action": "UP", "keycode": "BUTTON_SELECT"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_START",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_START"},
+      {"action": "UP", "keycode": "BUTTON_START"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_HOME",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_MODE"},
+      {"action": "UP", "keycode": "BUTTON_MODE"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_kishi_motioneventtests.json b/tests/tests/hardware/res/raw/razer_kishi_motioneventtests.json
new file mode 100644
index 0000000..8323ce2
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_kishi_motioneventtests.json
@@ -0,0 +1,214 @@
+[
+  {
+    "name": "Sanity check - should not produce any events",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+    ]
+  },
+
+  {
+    "name": "Press left DPAD key",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press right DPAD key",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press up DPAD key",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Press down DPAD key",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press left",
+    "reports": [
+        [0xc0, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press right",
+    "reports": [
+        [0x3f, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x7f, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press up",
+    "reports": [
+        [0x00, 0xc0, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press down",
+    "reports": [
+        [0x00, 0x3f, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x7f, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press left",
+    "reports": [
+        [0x00, 0x00, 0xc0, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press right",
+    "reports": [
+        [0x00, 0x00, 0x3f, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x7f, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press up",
+    "reports": [
+        [0x00, 0x00, 0x00, 0xc0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press down",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x3f, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x7f, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Left trigger - quick press",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x7f],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0xff],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.5, "AXIS_BRAKE": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 1.0, "AXIS_BRAKE": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.0, "AXIS_BRAKE": 0.0}}
+    ]
+  },
+
+  {
+    "name": "Right trigger - quick press",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x02, 0x00, 0x7f, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x02, 0x00, 0xff, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.5, "AXIS_GAS": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 1.0, "AXIS_GAS": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.0, "AXIS_GAS": 0.0}}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_kishi_register.json b/tests/tests/hardware/res/raw/razer_kishi_register.json
new file mode 100644
index 0000000..f66faae
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_kishi_register.json
@@ -0,0 +1,21 @@
+{
+    "id": 1,
+    "command": "register",
+    "name": "Razer Kishi (USB Test)",
+    "vid": 0x27f8,
+    "pid": 0x0bbf,
+    "bus": "usb",
+    "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0xa1, 0x02, 0x15, 0x81, 0x25, 0x7f, 0x05,
+        0x01, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95, 0x04, 0x35, 0x00, 0x46, 0xff, 0x00, 0x09,
+        0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, 0x75, 0x08, 0x95, 0x02, 0x15, 0x01,
+        0x26, 0xff, 0x00, 0x09, 0x39, 0x09, 0x39, 0x81, 0x02, 0xc0, 0x05, 0x07, 0x19, 0x4f, 0x29,
+        0x52, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02, 0x0a, 0xf1, 0x00, 0x15,
+        0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x01, 0x81, 0x02, 0x75, 0x01, 0x95, 0x02, 0x81, 0x03,
+        0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x0a, 0x24, 0x02, 0x75, 0x01,
+        0x95, 0x01, 0x81, 0x06, 0xc0, 0x75, 0x01, 0x95, 0x10, 0x05, 0x09, 0x19, 0x01, 0x29, 0x10,
+        0x81, 0x02, 0x06, 0x02, 0xff, 0x09, 0x01, 0xa1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x09, 0x04,
+        0x75, 0x01, 0x95, 0x01, 0x81, 0x02, 0xc0, 0x75, 0x01, 0x95, 0x07, 0x81, 0x03, 0x15, 0x00,
+        0x25, 0xff, 0x05, 0x02, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95, 0x02, 0x35, 0x00, 0x45,
+        0xff, 0x09, 0xc4, 0x09, 0xc5, 0x81, 0x02, 0xc0, 0x06, 0x00, 0xff, 0x09, 0x80, 0x75, 0x08,
+        0x95, 0x08, 0x15, 0x00, 0x26, 0xff, 0x00, 0xb1, 0x02, 0xc0, 0xc0]
+}
diff --git a/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_homekey.json b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_homekey.json
new file mode 100644
index 0000000..f3d81d7
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_homekey.json
@@ -0,0 +1,12 @@
+[
+  {
+    "name": "Press BUTTON_HOME",
+    "reports": [
+      [0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "events": [
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_keyeventtests.json b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_keyeventtests.json
new file mode 100644
index 0000000..26a4523
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_keyeventtests.json
@@ -0,0 +1,184 @@
+[
+  {
+    "name": "Press BUTTON_A",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_A"},
+      {"action": "UP", "keycode": "BUTTON_A"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_B",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_B"},
+      {"action": "UP", "keycode": "BUTTON_B"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_X",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_X"},
+      {"action": "UP", "keycode": "BUTTON_X"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_Y",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_Y"},
+      {"action": "UP", "keycode": "BUTTON_Y"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L1",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L1"},
+      {"action": "UP", "keycode": "BUTTON_L1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R1",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R1"},
+      {"action": "UP", "keycode": "BUTTON_R1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L2",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L2"},
+      {"action": "UP", "keycode": "BUTTON_L2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R2",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R2"},
+      {"action": "UP", "keycode": "BUTTON_R2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L3",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+      {"action": "UP", "keycode": "BUTTON_THUMBL"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R3",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+      {"action": "UP", "keycode": "BUTTON_THUMBR"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_SELECT",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+      {"action": "UP", "keycode": "BUTTON_SELECT"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_START",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_START"},
+      {"action": "UP", "keycode": "BUTTON_START"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_HOME",
+    "reports": [
+      [0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_MODE"},
+      {"action": "UP", "keycode": "BUTTON_MODE"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_BACK",
+    "reports": [
+      [0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BACK"},
+      {"action": "UP", "keycode": "BACK"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_motioneventtests.json b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_motioneventtests.json
new file mode 100644
index 0000000..2d802c9
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_motioneventtests.json
@@ -0,0 +1,214 @@
+[
+  {
+    "name": "Sanity check - should not produce any events",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+    ]
+  },
+
+  {
+    "name": "Press left DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press right DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press up DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Press down DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press left",
+    "reports": [
+      [0x01, 0x40, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press right",
+    "reports": [
+      [0x01, 0xc0, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0xff, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press up",
+    "reports": [
+      [0x01, 0x80, 0x40, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x00, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press down",
+    "reports": [
+      [0x01, 0x80, 0xc0, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0xff, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press left",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x40, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x00, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press right",
+    "reports": [
+      [0x01, 0x80, 0x80, 0xc0, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0xff, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press up",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press down",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0xc0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Left trigger - quick press",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x10, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x10, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.5, "AXIS_BRAKE": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 1.0, "AXIS_BRAKE": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.0, "AXIS_BRAKE": 0.0}}
+    ]
+  },
+
+  {
+    "name": "Right trigger - quick press",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x20, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x20, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.5, "AXIS_GAS": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 1.0, "AXIS_GAS": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.0, "AXIS_GAS": 0.0}}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_register.json b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_register.json
new file mode 100644
index 0000000..8ac2685
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_register.json
@@ -0,0 +1,21 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "Razer Raiju Mobile(Bluetooth Test)",
+  "vid": 0x1532,
+  "pid": 0x0707,
+  "bus": "bluetooth",
+  "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32,
+        0x09, 0x35, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, 0x09, 0x39,
+        0x15, 0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3b, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01,
+        0x81, 0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0f, 0x15, 0x00, 0x25, 0x01, 0x75,
+        0x01, 0x95, 0x0f, 0x81, 0x02, 0x75, 0x0d, 0x95, 0x01, 0x81, 0x03, 0x05, 0x02, 0x09, 0xc5,
+        0x09, 0xc4, 0x15, 0x00, 0x26, 0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08, 0x95,
+        0x02, 0x81, 0x02, 0x06, 0x00, 0xff, 0x09, 0x21, 0x95, 0x02, 0x81, 0x02, 0x85, 0x03, 0x0a,
+        0x21, 0x27, 0x95, 0x2f, 0xb1, 0x02, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x02,
+        0x0a, 0x23, 0x02, 0x0a, 0x24, 0x02, 0x09, 0x40, 0x09, 0xe9, 0x09, 0xea, 0x09, 0x30, 0x09,
+        0x32, 0x0a, 0xa2, 0x01, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x75,
+        0x01, 0x95, 0x08, 0x81, 0x03, 0x75, 0x08, 0x95, 0x0a, 0x81, 0x01, 0xc0, 0x05, 0x01, 0x09,
+        0x00, 0xa1, 0x01, 0x85, 0x07, 0x09, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x35, 0x00, 0x46,
+        0xff, 0x00, 0x75, 0x08, 0x95, 0x0c, 0x81, 0x00, 0xc0]
+}
diff --git a/tests/tests/hardware/res/raw/razer_serval_homekey.json b/tests/tests/hardware/res/raw/razer_serval_homekey.json
new file mode 100644
index 0000000..0b39087
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_serval_homekey.json
@@ -0,0 +1,11 @@
+[
+  {
+    "name": "Press HOME",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x80, 0x00, 0x00, 0x00, 0xff],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+    ],
+    "events": [
+    ]
+  }
+]
\ No newline at end of file
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_vibratortests.json b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_vibratortests.json
new file mode 100644
index 0000000..7708a74
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_vibratortests.json
@@ -0,0 +1,41 @@
+// Refer to kernel dualshock4_send_output_report() in drivers/hid/hid-sony.c
+[
+  {
+    "id": 1,
+    "durations" : [1000],
+    "amplitudes" : [192],
+    "leftFfIndex": 7,
+    "rightFfIndex": 6,
+    "output" :
+        [
+            {"index" : 0,
+            "data" : 0x11},   // DUALSHOCK4_CONTROLLER_BLUETOOTH
+
+            {"index" : 1,
+             "data" : 0xc4},   // HID + CRC | sc->ds4_bt_poll_interval
+
+            {"index" : 3,
+             "data" : 0x7}    // blink + LED + motor
+        ]
+  },
+
+  {
+    "id": 1,
+    "durations" : [2000, 2000, 2000, 2000, 2000],
+    "amplitudes" : [16, 32, 64, 128, 255],
+    "leftFfIndex": 7,
+    "rightFfIndex": 6,
+    "output" :
+        [
+            {"index" : 0,
+            "data" : 0x11},   // DUALSHOCK4_CONTROLLER_BLUETOOTH
+
+            {"index" : 1,
+             "data" : 0xc4},   // HID + CRC | sc->ds4_bt_poll_interval
+
+            {"index" : 3,
+             "data" : 0x7}    // blink + LED + motor
+        ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_usb_vibratortests.json b/tests/tests/hardware/res/raw/sony_dualshock4_usb_vibratortests.json
new file mode 100644
index 0000000..3bf7eaa
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_usb_vibratortests.json
@@ -0,0 +1,41 @@
+// Refer to kernel dualshock4_send_output_report() in drivers/hid/hid-sony.c
+[
+  {
+    "id": 1,
+    "durations" : [1000],
+    "amplitudes" : [192],
+    "leftFfIndex": 5,
+    "rightFfIndex": 4,
+    "output" :
+        [
+            {"index" : 0,
+            "data" : 0x5},   // DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE
+
+            {"index" : 1,
+             "data" : 0x7},   // blink + LED + motor
+
+            {"index" : 3,
+             "data" : 0x0}
+        ]
+  },
+
+  {
+    "id": 1,
+    "durations" : [2000, 2000, 2000, 2000, 2000],
+    "amplitudes" : [16, 32, 64, 128, 255],
+    "leftFfIndex": 5,
+    "rightFfIndex": 4,
+    "output" :
+        [
+          {"index" : 0,
+            "data" : 0x5},   // DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE
+
+            {"index" : 1,
+             "data" : 0x7},   // blink + LED + motor
+
+            {"index" : 3,
+             "data" : 0x0}
+        ]
+  }
+
+]
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java b/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
index 028b18e..5133406 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
@@ -18,13 +18,17 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
+import java.util.ArrayList;
+
 public class InputCtsActivity extends Activity {
     private static final String TAG = "InputCtsActivity";
 
     private InputCallback mInputCallback;
+    private final ArrayList<Integer> mUnhandledKeys = new ArrayList<>();
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -57,6 +61,11 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent ev) {
+        // Do not handle keys in UnhandledKeys list, let it fallback
+        if (mUnhandledKeys.contains(ev.getKeyCode())) {
+            Log.i(TAG, "Unhandled keyEvent: "  + ev);
+            return false;
+        }
         if (mInputCallback != null) {
             mInputCallback.onKeyEvent(ev);
         }
@@ -66,4 +75,12 @@
     public void setInputCallback(InputCallback callback) {
         mInputCallback = callback;
     }
+
+    public void addUnhandleKeyCode(int keyCode) {
+        mUnhandledKeys.add(keyCode);
+    }
+
+    public void clearUnhandleKeyCode() {
+        mUnhandledKeys.clear();
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTest.java
index 943a9fa..ed6bede 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTest.java
@@ -26,7 +26,7 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
-public class AsusGamepadTest extends InputTestCase {
+public class AsusGamepadTest extends InputHidTestCase {
     public AsusGamepadTest() {
         super(R.raw.asus_gamepad_register);
     }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GameviceGv186Test.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/GameviceGv186Test.java
new file mode 100644
index 0000000..89a2d8c
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/GameviceGv186Test.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.cts.R;
+import android.view.MotionEvent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class GameviceGv186Test extends InputHidTestCase {
+    private static final float TOLERANCE = 0.005f;
+
+    public GameviceGv186Test() {
+        super(R.raw.gamevice_gv186_register);
+    }
+
+    @Override
+    void assertAxis(String testCase, MotionEvent expectedEvent, MotionEvent actualEvent) {
+        for (int axis = MotionEvent.AXIS_X; axis <= MotionEvent.AXIS_GENERIC_16; axis++) {
+            // Skip checking AXIS_GENERIC_1 and AXIS_GENERIC_2, this device has HID usage of DPAD
+            // which maps to HAT1X and HAT1Y, which have non zero axes values always.
+            if (axis == MotionEvent.AXIS_GENERIC_1 || axis == MotionEvent.AXIS_GENERIC_2) {
+                continue;
+            }
+            assertEquals(testCase + " (" + MotionEvent.axisToString(axis) + ")",
+                    expectedEvent.getAxisValue(axis), actualEvent.getAxisValue(axis), TOLERANCE);
+        }
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.gamevice_gv186_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.gamevice_gv186_motioneventtests);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
new file mode 100644
index 0000000..c56e51e
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.hardware.input.InputManager;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.Log;
+import android.view.InputDevice;
+
+import com.android.cts.input.HidDevice;
+import com.android.cts.input.HidResultData;
+import com.android.cts.input.HidTestData;
+import com.android.cts.input.HidVibratorTestData;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public class InputHidTestCase extends InputTestCase {
+    private static final String TAG = "InputHidTestCase";
+    // Sync with linux uhid_event_type::UHID_OUTPUT
+    private static final byte UHID_EVENT_TYPE_UHID_OUTPUT = 6;
+
+    private HidDevice mHidDevice;
+    private int mDeviceId;
+    private final int mRegisterResourceId;
+
+    InputHidTestCase(int registerResourceId) {
+        super(registerResourceId);
+        mRegisterResourceId = registerResourceId;
+    }
+
+    /**
+     * Get a vibrator from input device with specified Vendor Id and Product Id.
+     * @return Vibrator object in specified InputDevice
+     */
+    private Vibrator getVibrator() {
+        final InputManager inputManager =
+                mInstrumentation.getTargetContext().getSystemService(InputManager.class);
+        final int[] inputDeviceIds = inputManager.getInputDeviceIds();
+        final int vid = mParser.readVendorId(mRegisterResourceId);
+        final int pid = mParser.readProductId(mRegisterResourceId);
+
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = inputManager.getInputDevice(inputDeviceId);
+            Vibrator vibrator = inputDevice.getVibrator();
+            if (vibrator.hasVibrator() && inputDevice.getVendorId() == vid
+                    && inputDevice.getProductId() == pid) {
+                Log.v(TAG, "Input device: " + inputDeviceId + " VendorId: "
+                        + inputDevice.getVendorId() + " ProductId: " + inputDevice.getProductId());
+                return vibrator;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void setUpDevice(int deviceId, String registerCommand) {
+        mDeviceId = deviceId;
+        mHidDevice = new HidDevice(mInstrumentation, deviceId, registerCommand);
+        assertNotNull(mHidDevice);
+    }
+
+    @Override
+    protected void tearDownDevice() {
+        mHidDevice.close();
+    }
+
+    @Override
+    protected void testInputDeviceEvents(int resourceId) {
+        List<HidTestData> tests = mParser.getHidTestData(resourceId);
+
+        for (HidTestData testData: tests) {
+            mCurrentTestCase = testData.name;
+
+            // Send all of the HID reports
+            for (int i = 0; i < testData.reports.size(); i++) {
+                final String report = testData.reports.get(i);
+                mHidDevice.sendHidReport(report);
+            }
+            verifyEvents(testData.events);
+
+        }
+    }
+
+    private boolean verifyReportData(HidVibratorTestData test, HidResultData result) {
+        for (Map.Entry<Integer, Integer> entry : test.verifyMap.entrySet()) {
+            final int index = entry.getKey();
+            final int value = entry.getValue();
+            if ((result.reportData[index] & 0XFF) != value) {
+                Log.v(TAG, "index=" + index + " value= " + value
+                        + "actual= " + (result.reportData[index] & 0XFF));
+                return false;
+            }
+        }
+        final int ffLeft = result.reportData[test.leftFfIndex] & 0xFF;
+        final int ffRight = result.reportData[test.rightFfIndex] & 0xFF;
+
+        return ffLeft > 0 && ffRight > 0;
+    }
+
+    public void testInputVibratorEvents(int resourceId) {
+        final List<HidVibratorTestData> tests = mParser.getHidVibratorTestData(resourceId);
+
+        for (HidVibratorTestData test : tests) {
+            assertEquals(test.durations.size(), test.amplitudes.size());
+            assertTrue(test.durations.size() > 0);
+
+            final long timeoutMills;
+            final long totalVibrations = test.durations.size();
+            final VibrationEffect effect;
+            if (test.durations.size() == 1) {
+                long duration = test.durations.get(0);
+                int amplitude = test.amplitudes.get(0);
+                effect = VibrationEffect.createOneShot(duration, amplitude);
+                // Set timeout to be 2 times of the effect duration.
+                timeoutMills = duration * 2;
+            } else {
+                long[] durations = test.durations.stream().mapToLong(Long::longValue).toArray();
+                int[] amplitudes = test.amplitudes.stream().mapToInt(Integer::intValue).toArray();
+                effect = VibrationEffect.createWaveform(
+                    durations, amplitudes, -1);
+                // Set timeout to be 2 times of the effect total duration.
+                timeoutMills = Arrays.stream(durations).sum() * 2;
+            }
+
+            final Vibrator vibrator = getVibrator();
+            assertNotNull(vibrator);
+            // Start vibration
+            vibrator.vibrate(effect);
+            final long startTime = SystemClock.elapsedRealtime();
+            List<HidResultData> results = new ArrayList<>();
+            int vibrationCount = 0;
+            // Check the vibration ffLeft and ffRight amplitude to be expected.
+            while (vibrationCount < totalVibrations
+                    && SystemClock.elapsedRealtime() - startTime < timeoutMills) {
+                SystemClock.sleep(1000);
+                try {
+                    results = mHidDevice.getResults(mDeviceId, UHID_EVENT_TYPE_UHID_OUTPUT);
+                    if (results.size() < totalVibrations) {
+                        continue;
+                    }
+                    vibrationCount = 0;
+                    for (int i = 0; i < results.size(); i++) {
+                        HidResultData result = results.get(i);
+                        if (result.deviceId == mDeviceId && verifyReportData(test, result)) {
+                            int ffLeft = result.reportData[test.leftFfIndex] & 0xFF;
+                            int ffRight = result.reportData[test.rightFfIndex] & 0xFF;
+                            Log.v(TAG, "eventId=" + result.eventId + " reportType="
+                                    + result.reportType + " left=" + ffLeft + " right=" + ffRight);
+                            // Check the amplitudes of FF effect are expected.
+                            if (ffLeft == test.amplitudes.get(vibrationCount)
+                                    && ffRight == test.amplitudes.get(vibrationCount)) {
+                                vibrationCount++;
+                            }
+                        }
+                    }
+                } catch (IOException ex) {
+                    throw new RuntimeException("Could not get JSON results from HidDevice");
+                }
+            }
+            assertEquals(vibrationCount, totalVibrations);
+        }
+    }
+
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
index 6239026..7015036 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
@@ -32,9 +32,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.cts.input.HidDevice;
-import com.android.cts.input.HidJsonParser;
-import com.android.cts.input.HidTestData;
+import com.android.cts.input.InputJsonParser;
 
 import org.junit.After;
 import org.junit.Before;
@@ -53,12 +51,12 @@
     private final BlockingQueue<InputEvent> mEvents;
 
     private InputListener mInputListener;
-    private Instrumentation mInstrumentation;
     private View mDecorView;
-    private HidDevice mHidDevice;
-    private HidJsonParser mParser;
+
+    protected Instrumentation mInstrumentation;
+    protected InputJsonParser mParser;
     // Stores the name of the currently running test
-    private String mCurrentTestCase;
+    protected String mCurrentTestCase;
     private int mRegisterResourceId; // raw resource that contains json for registering a hid device
 
     // State used for motion events
@@ -78,19 +76,27 @@
     public void setUp() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivityRule.getActivity().setInputCallback(mInputListener);
+        mActivityRule.getActivity().clearUnhandleKeyCode();
         mDecorView = mActivityRule.getActivity().getWindow().getDecorView();
-        mParser = new HidJsonParser(mInstrumentation.getTargetContext());
-        int hidDeviceId = mParser.readDeviceId(mRegisterResourceId);
+        mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+        int deviceId = mParser.readDeviceId(mRegisterResourceId);
         String registerCommand = mParser.readRegisterCommand(mRegisterResourceId);
-        mHidDevice = new HidDevice(mInstrumentation, hidDeviceId, registerCommand);
+        setUpDevice(deviceId, registerCommand);
         mEvents.clear();
     }
 
     @After
     public void tearDown() {
-        mHidDevice.close();
+        tearDownDevice();
     }
 
+    // To be implemented by device specific test case.
+    protected abstract void setUpDevice(int deviceId, String registerCommand);
+
+    protected abstract void tearDownDevice();
+
+    protected abstract void testInputDeviceEvents(int resourceId);
+
     /**
      * Asserts that the application received a {@link android.view.KeyEvent} with the given
      * metadata.
@@ -111,8 +117,8 @@
         assertSource(mCurrentTestCase, expectedKeyEvent.getSource(), receivedKeyEvent.getSource());
         assertEquals(mCurrentTestCase + " (keycode)",
                 expectedKeyEvent.getKeyCode(), receivedKeyEvent.getKeyCode());
-        assertEquals(mCurrentTestCase + " (meta state)",
-                expectedKeyEvent.getMetaState(), receivedKeyEvent.getMetaState());
+        assertMetaState(mCurrentTestCase, expectedKeyEvent.getMetaState(),
+                receivedKeyEvent.getMetaState());
     }
 
     private void assertReceivedMotionEvent(@NonNull MotionEvent expectedEvent) {
@@ -146,9 +152,20 @@
                     mLastButtonState ^ event.getButtonState(), event.getActionButton());
             mLastButtonState = event.getButtonState();
         }
+        assertAxis(mCurrentTestCase, expectedEvent, event);
+    }
+
+    /**
+     * Asserts motion event axis values. Separate this into a different method to allow individual
+     * test case to specify it.
+     *
+     * @param expectedSource expected source flag specified in JSON files.
+     * @param actualSource actual source flag received in the test app.
+     */
+    void assertAxis(String testCase, MotionEvent expectedEvent, MotionEvent actualEvent) {
         for (int axis = MotionEvent.AXIS_X; axis <= MotionEvent.AXIS_GENERIC_16; axis++) {
-            assertEquals(mCurrentTestCase + " (" + MotionEvent.axisToString(axis) + ")",
-                    expectedEvent.getAxisValue(axis), event.getAxisValue(axis), TOLERANCE);
+            assertEquals(testCase + " (" + MotionEvent.axisToString(axis) + ")",
+                    expectedEvent.getAxisValue(axis), actualEvent.getAxisValue(axis), TOLERANCE);
         }
     }
 
@@ -164,6 +181,17 @@
     }
 
     /**
+     * Asserts meta states. Separate this into a different method to allow individual test case to
+     * specify it.
+     *
+     * @param expectedMetaState expected meta state specified in JSON files.
+     * @param actualMetaState actual meta state received in the test app.
+     */
+    void assertMetaState(String testCase, int expectedMetaState, int actualMetaState) {
+        assertEquals(testCase + " (meta state)", expectedMetaState, actualMetaState);
+    }
+
+    /**
      * Assert that no more events have been received by the application.
      *
      * If any more events have been received by the application, this will cause failure.
@@ -177,66 +205,70 @@
         failWithMessage("extraneous events generated: " + event);
     }
 
-    protected void testInputEvents(int resourceId) {
-        List<HidTestData> tests = mParser.getTestData(resourceId);
-
-        for (HidTestData testData: tests) {
-            mCurrentTestCase = testData.name;
-
-            // Send all of the HID reports
-            for (int i = 0; i < testData.reports.size(); i++) {
-                final String report = testData.reports.get(i);
-                mHidDevice.sendHidReport(report);
+    protected void verifyEvents(List<InputEvent> events) {
+        // Make sure we received the expected input events
+        if (events.size() == 0) {
+            // If no event is expected we need to wait for event until timeout and fail on
+            // any unexpected event received caused by the HID report injection.
+            InputEvent event = waitForEvent();
+            if (event != null) {
+                fail("Received unexpected event " + event);
             }
-
-            // Make sure we received the expected input events
-            for (int i = 0; i < testData.events.size(); i++) {
-                final InputEvent event = testData.events.get(i);
-                try {
-                    if (event instanceof MotionEvent) {
-                        assertReceivedMotionEvent((MotionEvent) event);
-                        continue;
-                    }
-                    if (event instanceof KeyEvent) {
-                        assertReceivedKeyEvent((KeyEvent) event);
-                        continue;
-                    }
-                } catch (AssertionError error) {
-                    throw new AssertionError("Assertion on entry " + i + " failed.", error);
-                }
-                fail("Entry " + i + " is neither a KeyEvent nor a MotionEvent: " + event);
-            }
+            return;
         }
+        for (int i = 0; i < events.size(); i++) {
+            final InputEvent event = events.get(i);
+            try {
+                if (event instanceof MotionEvent) {
+                    assertReceivedMotionEvent((MotionEvent) event);
+                    continue;
+                }
+                if (event instanceof KeyEvent) {
+                    assertReceivedKeyEvent((KeyEvent) event);
+                    continue;
+                }
+            } catch (AssertionError error) {
+                throw new AssertionError("Assertion on entry " + i + " failed.", error);
+            }
+            fail("Entry " + i + " is neither a KeyEvent nor a MotionEvent: " + event);
+        }
+        assertNoMoreEvents();
+    }
+
+    protected void testInputEvents(int resourceId) {
+        testInputDeviceEvents(resourceId);
         assertNoMoreEvents();
     }
 
     private InputEvent waitForEvent() {
         try {
-            return mEvents.poll(5, TimeUnit.SECONDS);
+            return mEvents.poll(1, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
             failWithMessage("unexpectedly interrupted while waiting for InputEvent");
             return null;
         }
     }
 
+    // Ignore Motion event received during the 5 seconds timeout period. Return on the first Key
+    // event received.
     private KeyEvent waitForKey() {
-        InputEvent event = waitForEvent();
-        if (event instanceof KeyEvent) {
-            return (KeyEvent) event;
-        }
-        if (event instanceof MotionEvent) {
-            failWithMessage("Instead of a key event, received " + event);
+        for (int i = 0; i < 5; i++) {
+            InputEvent event = waitForEvent();
+            if (event instanceof KeyEvent) {
+                return (KeyEvent) event;
+            }
         }
         return null;
     }
 
+    // Ignore Key event received during the 5 seconds timeout period. Return on the first Motion
+    // event received.
     private MotionEvent waitForMotion() {
-        InputEvent event = waitForEvent();
-        if (event instanceof MotionEvent) {
-            return (MotionEvent) event;
-        }
-        if (event instanceof KeyEvent) {
-            failWithMessage("Instead of a motion event, received " + event);
+        for (int i = 0; i < 5; i++) {
+            InputEvent event = waitForEvent();
+            if (event instanceof MotionEvent) {
+                return (MotionEvent) event;
+            }
         }
         return null;
     }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java
new file mode 100644
index 0000000..d0f309e
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import com.android.cts.input.UinputDevice;
+import com.android.cts.input.UinputTestData;
+
+import java.util.List;
+
+public class InputUinputTestCase extends InputTestCase {
+    private static final String TAG = "InputUinputTestCase";
+    private UinputDevice mUinputDevice;
+
+    InputUinputTestCase(int registerResourceId) {
+        super(registerResourceId);
+    }
+
+    @Override
+    protected void setUpDevice(int deviceId, String registerCommand) {
+        mUinputDevice = new UinputDevice(mInstrumentation, deviceId, registerCommand);
+    }
+
+    @Override
+    protected void tearDownDevice() {
+        mUinputDevice.close();
+    }
+
+    @Override
+    protected void testInputDeviceEvents(int resourceId) {
+        List<UinputTestData> tests = mParser.getUinputTestData(resourceId);
+
+        for (UinputTestData testData: tests) {
+            mCurrentTestCase = testData.name;
+
+            // Send all of the evdev Events
+            for (int i = 0; i < testData.evdevEvents.size(); i++) {
+                final String injections = testData.evdevEvents.get(i);
+                mUinputDevice.injectEvents(injections);
+            }
+            verifyEvents(testData.events);
+        }
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java
index 5712ca7..d15b08a 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 
 import android.hardware.cts.R;
+import android.view.KeyEvent;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -28,7 +29,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class MicrosoftDesignerKeyboardTest extends InputTestCase {
+public class MicrosoftDesignerKeyboardTest extends InputHidTestCase {
 
     public MicrosoftDesignerKeyboardTest() {
         super(R.raw.microsoft_designer_keyboard_register);
@@ -51,4 +52,16 @@
     void assertSource(String testCase, int expectedSource, int actualSource) {
         assertEquals(testCase + " (source)", expectedSource & actualSource, actualSource);
     }
+
+    /**
+     * Microsoft Designer Keyboard has meta control keys of NUM_LOCK, CAPS_LOCK and SCROLL_LOCK.
+     * Do not verify the meta key states that have global state and initially to be on.
+     */
+    @Override
+    void assertMetaState(String testCase, int expectedMetaState, int actualMetaState) {
+        final int metaStates = KeyEvent.META_NUM_LOCK_ON | KeyEvent.META_CAPS_LOCK_ON
+                | KeyEvent.META_SCROLL_LOCK_ON;
+        actualMetaState &= ~metaStates;
+        assertEquals(testCase + " (meta state)", expectedMetaState , actualMetaState);
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java
index 50ac183..4e10b36 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class MicrosoftSculpttouchTest extends InputTestCase {
+public class MicrosoftSculpttouchTest extends InputHidTestCase {
 
     public MicrosoftSculpttouchTest() {
         super(R.raw.microsoft_sculpttouch_register);
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXbox2020Test.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXbox2020Test.java
index 74e74ea..387ead3 100755
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXbox2020Test.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXbox2020Test.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class MicrosoftXbox2020Test extends InputTestCase {
+public class MicrosoftXbox2020Test extends InputHidTestCase {
 
     // Exercises the Bluetooth behavior of the Xbox One S controller
     public MicrosoftXbox2020Test() {
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXboxOneSTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXboxOneSTest.java
index ae4a30d..fab8b50 100755
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXboxOneSTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXboxOneSTest.java
@@ -26,7 +26,7 @@
 

 @SmallTest

 @RunWith(AndroidJUnit4.class)

-public class MicrosoftXboxOneSTest extends InputTestCase {

+public class MicrosoftXboxOneSTest extends InputHidTestCase {

 

     // Exercises the Bluetooth behavior of the Xbox One S controller

     public MicrosoftXboxOneSTest() {

diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java
index c826b59..d235428 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java
@@ -28,7 +28,7 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
-public class NintendoSwitchProTest extends InputTestCase {
+public class NintendoSwitchProTest extends InputHidTestCase {
     public NintendoSwitchProTest() {
         super(R.raw.nintendo_switchpro_register);
     }
@@ -39,7 +39,7 @@
         /**
          * During probe, hid-nintendo sends commands to the joystick and waits for some of those
          * commands to execute. Somewhere in the middle of the commands, the driver will register
-         * an input device, which is the notification received by InputTestCase.
+         * an input device, which is the notification received by InputHidTestCase.
          * If a command is still being waited on while we start writing
          * events to uhid, all incoming events are dropped, because probe() still hasn't finished.
          * To ensure that hid-nintendo probe is done, add a delay here.
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerJunglecatBluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerJunglecatBluetoothTest.java
new file mode 100644
index 0000000..5a9a8cd
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerJunglecatBluetoothTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RazerJunglecatBluetoothTest extends InputHidTestCase {
+
+    // Simulates the behavior of Razer Junglecat gamepad.
+    public RazerJunglecatBluetoothTest() {
+        super(R.raw.razer_junglecat_register);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.razer_junglecat_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.razer_junglecat_motioneventtests);
+    }
+
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerKishiTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerKishiTest.java
new file mode 100644
index 0000000..5a11d7f
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerKishiTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RazerKishiTest extends InputHidTestCase {
+
+    // Simulates the behavior of Razer Kishi gamepad.
+    public RazerKishiTest() {
+        super(R.raw.razer_kishi_register);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.razer_kishi_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.razer_kishi_motioneventtests);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java
new file mode 100644
index 0000000..210b368
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+import android.server.wm.WindowManagerStateHelper;
+import android.view.KeyEvent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RazerRaijuMobileBluetoothTest extends InputHidTestCase {
+
+    // Simulates the behavior of Razer Raiju Mobile gamepad.
+    public RazerRaijuMobileBluetoothTest() {
+        super(R.raw.razer_raiju_mobile_bluetooth_register);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.razer_raiju_mobile_bluetooth_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.razer_raiju_mobile_bluetooth_motioneventtests);
+    }
+
+    /**
+     * Add BUTTON_MODE to the activity's unhandled keys list to allow the fallback
+     * of HOME. Do home button behavior check with wm utils.
+     */
+    @Test
+    public void testHomeKey() throws Exception {
+        mActivityRule.getActivity().addUnhandleKeyCode(KeyEvent.KEYCODE_BUTTON_MODE);
+        testInputEvents(R.raw.razer_raiju_mobile_bluetooth_homekey);
+
+        WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
+
+        wmStateHelper.waitForHomeActivityVisible();
+        wmStateHelper.assertHomeActivityVisible(true);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
index 2122085..030c030 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
@@ -17,6 +17,7 @@
 package android.hardware.input.cts.tests;
 
 import android.hardware.cts.R;
+import android.server.wm.WindowManagerStateHelper;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
@@ -26,7 +27,7 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
-public class RazerServalTest extends InputTestCase {
+public class RazerServalTest extends InputHidTestCase {
     public RazerServalTest() {
         super(R.raw.razer_serval_register);
     }
@@ -43,4 +44,18 @@
     public void testAllMotions() {
         testInputEvents(R.raw.razer_serval_motioneventtests);
     }
+
+    /**
+     * We cannot test the home key using "testAllKeys" because the home key does not go to the
+     * apps, and therefore cannot be received in InputCtsActivity.
+     * Instead, we rely on the home button behavior check using the wm utils.
+     */
+    @Test
+    public void testHomeKey() {
+        testInputEvents(R.raw.razer_serval_homekey);
+        WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
+
+        wmStateHelper.waitForHomeActivityVisible();
+        wmStateHelper.assertHomeActivityVisible(true);
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java
index e65fa7a..6f4762f 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SonyDualshock3UsbTest extends InputTestCase {
+public class SonyDualshock3UsbTest extends InputHidTestCase {
 
     // Simulates the behavior of PlayStation DualShock3 gamepad (model CECHZC2U)
     public SonyDualshock3UsbTest() {
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
index ca36a68..04b4acc 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SonyDualshock4BluetoothTest extends InputTestCase {
+public class SonyDualshock4BluetoothTest extends InputHidTestCase {
 
     // Simulates the behavior of PlayStation DualShock4 gamepad (model CUH-ZCT1U)
     public SonyDualshock4BluetoothTest() {
@@ -42,4 +42,10 @@
     public void testAllMotions() {
         testInputEvents(R.raw.sony_dualshock4_bluetooth_motioneventtests);
     }
+
+    @Test
+    public void testVibrator() {
+        testInputVibratorEvents(R.raw.sony_dualshock4_bluetooth_vibratortests);
+    }
+
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
index c5f761f..5c83ab5 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SonyDualshock4ProBluetoothTest extends InputTestCase {
+public class SonyDualshock4ProBluetoothTest extends InputHidTestCase {
 
     // Simulates the behavior of PlayStation DualShock4 Pro gamepad (model CUH-ZCT2U)
     public SonyDualshock4ProBluetoothTest() {
@@ -42,4 +42,10 @@
     public void testAllMotions() {
         testInputEvents(R.raw.sony_dualshock4_bluetooth_motioneventtests);
     }
+
+    @Test
+    public void testVibrator() {
+        testInputVibratorEvents(R.raw.sony_dualshock4_bluetooth_vibratortests);
+    }
+
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProUsbTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProUsbTest.java
index 8e967ff..5dd82aa 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProUsbTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProUsbTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SonyDualshock4ProUsbTest extends InputTestCase {
+public class SonyDualshock4ProUsbTest extends InputHidTestCase {
 
     // Simulates the behavior of PlayStation DualShock4 Pro gamepad (model CUH-ZCT2U)
     public SonyDualshock4ProUsbTest() {
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
index 062dced..493afb2 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SonyDualshock4UsbTest extends InputTestCase {
+public class SonyDualshock4UsbTest extends InputHidTestCase {
 
     // Simulates the behavior of PlayStation DualShock4 gamepad (model CUH-ZCT1U)
     public SonyDualshock4UsbTest() {
@@ -42,4 +42,9 @@
     public void testAllMotions() {
         testInputEvents(R.raw.sony_dualshock4_usb_motioneventtests);
     }
+
+    @Test
+    public void testVibrator() {
+        testInputVibratorEvents(R.raw.sony_dualshock4_usb_vibratortests);
+    }
 }
diff --git a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
index daef758..87256cc 100644
--- a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
+++ b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
@@ -331,33 +331,16 @@
         JNIEnv* env,
         jclass clazz,
         jobjectArray java_system_public_libraries,
-        jobjectArray java_runtime_public_libraries,
-        jobjectArray java_vendor_public_libraries,
-        jobjectArray java_product_public_libraries) {
+        jobjectArray java_runtime_public_libraries) {
   bool success = true;
   std::vector<std::string> errors;
   std::string error_msg;
-  std::unordered_set<std::string> vendor_public_libraries;
-  if (!jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries,
-                            &error_msg)) {
-    success = false;
-    errors.push_back("Errors in vendor public library file:" + error_msg);
-  }
-
   std::unordered_set<std::string> system_public_libraries;
   if (!jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries,
                             &error_msg)) {
     success = false;
     errors.push_back("Errors in system public library file:" + error_msg);
   }
-
-  std::unordered_set<std::string> product_public_libraries;
-  if (!jobject_array_to_set(env, java_product_public_libraries, &product_public_libraries,
-                            &error_msg)) {
-    success = false;
-    errors.push_back("Errors in product public library file:" + error_msg);
-  }
-
   std::unordered_set<std::string> runtime_public_libraries;
   if (!jobject_array_to_set(env, java_runtime_public_libraries, &runtime_public_libraries,
                             &error_msg)) {
@@ -411,21 +394,6 @@
     success = false;
   }
 
-  // Check the product libraries, if /product/lib exists.
-  if (is_directory(kProductLibraryPath.c_str())) {
-    if (!check_path(env, clazz, kProductLibraryPath, {kProductLibraryPath},
-                    product_public_libraries,
-                    /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) {
-      success = false;
-    }
-  }
-
-  // Check the vendor libraries.
-  if (!check_path(env, clazz, kVendorLibraryPath, {kVendorLibraryPath}, vendor_public_libraries,
-                  /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) {
-    success = false;
-  }
-
   if (!success) {
     std::string error_str;
     for (const auto& line : errors) {
diff --git a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
index 6cd2441..08267cc 100644
--- a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
+++ b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
@@ -188,7 +188,7 @@
 
         Collections.addAll(systemLibs, PUBLIC_SYSTEM_LIBRARIES);
         Collections.addAll(systemLibs, OPTIONAL_SYSTEM_LIBRARIES);
-        // System path could contain public ART libraries on foreign arch. http://b/149852946
+	// System path could contain public ART libraries on foreign arch. http://b/149852946
         if (isForeignArchitecture()) {
             Collections.addAll(systemLibs, PUBLIC_ART_LIBRARIES);
         }
@@ -200,22 +200,20 @@
 
         Collections.addAll(artApexLibs, PUBLIC_ART_LIBRARIES);
 
-        // Check if public.libraries.txt contains libs other than the
-        // public system libs (NDK libs).
+        // Check if /system/etc/public.libraries-company.txt and /product/etc/public.libraries
+        // -company.txt files are well-formed. The libraries however are not loaded for test;
+        // It is done in another test CtsUsesNativeLibraryTest because since Android S those libs
+        // are not available unless they are explicited listed in the app manifest.
 
         List<String> oemLibs = new ArrayList<>();
         String oemLibsError = readExtensionConfigFiles(PUBLIC_CONFIG_DIR, oemLibs);
         if (oemLibsError != null) return oemLibsError;
-        // OEM libs that passed above tests are available to Android app via JNI
-        systemLibs.addAll(oemLibs);
 
         // PRODUCT libs that passed are also available
         List<String> productLibs = new ArrayList<>();
         String productLibsError = readExtensionConfigFiles(PRODUCT_CONFIG_DIR, productLibs);
         if (productLibsError != null) return productLibsError;
 
-        List<String> vendorLibs = readPublicLibrariesFile(new File(VENDOR_CONFIG_FILE));
-
         // Make sure that the libs in grey-list are not exposed to apps. In fact, it
         // would be better for us to run this check against all system libraries which
         // are not NDK libs, but grey-list libs are enough for now since they have been
@@ -225,6 +223,7 @@
         // Note: check for systemLibs isn't needed since we already checked
         // /system/etc/public.libraries.txt against NDK and
         // /system/etc/public.libraries-<company>.txt against lib<name>.<company>.so.
+        List<String> vendorLibs = readPublicLibrariesFile(new File(VENDOR_CONFIG_FILE));
         for (String lib : vendorLibs) {
             if (greyListLibs.contains(lib)) {
                 return "Internal library \"" + lib + "\" must not be available to apps.";
@@ -232,15 +231,11 @@
         }
 
         return runAccessibilityTestImpl(systemLibs.toArray(new String[systemLibs.size()]),
-                                        artApexLibs.toArray(new String[artApexLibs.size()]),
-                                        vendorLibs.toArray(new String[vendorLibs.size()]),
-                                        productLibs.toArray(new String[productLibs.size()]));
+                                        artApexLibs.toArray(new String[artApexLibs.size()]));
     }
 
     private static native String runAccessibilityTestImpl(String[] publicSystemLibs,
-                                                          String[] publicRuntimeLibs,
-                                                          String[] publicVendorLibs,
-                                                          String[] publicProductLibs);
+                                                          String[] publicRuntimeLibs);
 
     private static void invokeIncrementGlobal(Class<?> clazz) throws Exception {
         clazz.getMethod("incrementGlobal").invoke(null);
@@ -386,27 +381,18 @@
     }
 
     public static String runDlopenPublicLibraries() {
-        String error;
-        try {
-            List<String> publicLibs = new ArrayList<>();
-            Collections.addAll(publicLibs, PUBLIC_SYSTEM_LIBRARIES);
-            Collections.addAll(publicLibs, PUBLIC_ART_LIBRARIES);
-            error = readExtensionConfigFiles(PUBLIC_CONFIG_DIR, publicLibs);
-            if (error != null) return error;
-            error = readExtensionConfigFiles(PRODUCT_CONFIG_DIR, publicLibs);
-            if (error != null) return error;
-            publicLibs.addAll(readPublicLibrariesFile(new File(VENDOR_CONFIG_FILE)));
-            for (String lib : publicLibs) {
-                String result = LinkerNamespacesHelper.tryDlopen(lib);
-                if (result != null) {
-                    if (error == null) {
-                        error = "";
-                    }
-                    error += result + "\n";
+        String error = null;
+        List<String> publicLibs = new ArrayList<>();
+        Collections.addAll(publicLibs, PUBLIC_SYSTEM_LIBRARIES);
+        Collections.addAll(publicLibs, PUBLIC_ART_LIBRARIES);
+        for (String lib : publicLibs) {
+            String result = LinkerNamespacesHelper.tryDlopen(lib);
+            if (result != null) {
+                if (error == null) {
+                    error = "";
                 }
+                error += result + "\n";
             }
-        } catch (IOException e) {
-            return e.toString();
         }
         return error;
     }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 194b6f6..a6ff813 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -151,6 +151,7 @@
         assertEquals(0, parseSystemOsVersion("99.99.100"));
     }
 
+    @RequiresDevice
     public void testEcAttestation() throws Exception {
         // Note: Curve and key sizes arrays must correspond.
         String[] curves = {
@@ -302,6 +303,7 @@
         }
     }
 
+    @RequiresDevice
     public void testRsaAttestation() throws Exception {
         int[] keySizes = { // Smallish sizes to keep test runtimes down.
                 512, 768, 1024
diff --git a/tests/tests/libcoreapievolution/TEST_MAPPING b/tests/tests/libcoreapievolution/TEST_MAPPING
new file mode 100644
index 0000000..40dc97e
--- /dev/null
+++ b/tests/tests/libcoreapievolution/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLibcoreApiEvolutionTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/libcorefileio/AndroidManifest.xml b/tests/tests/libcorefileio/AndroidManifest.xml
index 1271089..7ef7c14 100644
--- a/tests/tests/libcorefileio/AndroidManifest.xml
+++ b/tests/tests/libcorefileio/AndroidManifest.xml
@@ -16,17 +16,17 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.libcorefileio.cts">
+     package="android.libcorefileio.cts">
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <application>
         <uses-library android:name="android.test.runner"/>
         <service android:name="android.cts.LockHoldingService"
-                 android:process=":lockHoldingService"
-                 android:permission="android.permission.WRITE_EXTERNAL_STORAGE"
-        />
-        <receiver android:name="android.cts.FileChannelInterProcessLockTest$IntentReceiver">
+             android:process=":lockHoldingService"
+             android:permission="android.permission.WRITE_EXTERNAL_STORAGE"/>
+        <receiver android:name="android.cts.FileChannelInterProcessLockTest$IntentReceiver"
+             android:exported="true">
 
             <intent-filter>
                 <action android:name="android.cts.CtsLibcoreFileIOTestCases">
@@ -37,10 +37,9 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.libcorefileio.cts">
+         android:targetPackage="android.libcorefileio.cts">
         <meta-data android:name="listener"
-                   android:value="com.android.cts.runner.CtsTestRunListener"/>
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/libcorelegacy22/TEST_MAPPING b/tests/tests/libcorelegacy22/TEST_MAPPING
new file mode 100644
index 0000000..acefbee
--- /dev/null
+++ b/tests/tests/libcorelegacy22/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLibcoreLegacy22TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/match_flags/app/a/AndroidManifest.xml b/tests/tests/match_flags/app/a/AndroidManifest.xml
index 8ad54de..6ad9415 100644
--- a/tests/tests/match_flags/app/a/AndroidManifest.xml
+++ b/tests/tests/match_flags/app/a/AndroidManifest.xml
@@ -16,16 +16,17 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.matchflags.app.uniqueandshared">
+     package="android.matchflags.app.uniqueandshared">
     <application>
         <uses-library android:name="android.test.runner"/>
-        <activity android:name="android.matchflags.cts.app.TestActivity">
+        <activity android:name="android.matchflags.cts.app.TestActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW"/>
                 <category android:name="android.intent.category.BROWSABLE"/>
                 <category android:name="android.intent.category.DEFAULT"/>
                 <data android:scheme="https"
-                      android:host="unique-5gle2bs6woovjn8xabwyb3js01xl0ducci3gd3fpe622h48lyg.com"/>
+                     android:host="unique-5gle2bs6woovjn8xabwyb3js01xl0ducci3gd3fpe622h48lyg.com"/>
             </intent-filter>
             <intent-filter>
                 <action android:name="android.matchflags.app.UNIQUE_ACTION"/>
diff --git a/tests/tests/match_flags/app/b/AndroidManifest.xml b/tests/tests/match_flags/app/b/AndroidManifest.xml
index 6d2c9d0..4a0d85a 100644
--- a/tests/tests/match_flags/app/b/AndroidManifest.xml
+++ b/tests/tests/match_flags/app/b/AndroidManifest.xml
@@ -16,10 +16,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.matchflags.app.shared">
+     package="android.matchflags.app.shared">
     <application>
         <uses-library android:name="android.test.runner"/>
-        <activity android:name="android.matchflags.cts.app.TestActivity">
+        <activity android:name="android.matchflags.cts.app.TestActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.matchflags.app.SHARED_ACTION"/>
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/tests/tests/media/Android.bp b/tests/tests/media/Android.bp
index b7c40ad..0e7222b 100644
--- a/tests/tests/media/Android.bp
+++ b/tests/tests/media/Android.bp
@@ -62,7 +62,10 @@
         "-0 .ota",
         "-0 .mxmf",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "aidl/**/*.aidl",
+    ],
     // This test uses private APIs
     //sdk_version: "current",
     platform_apis: true,
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index e316b4d..fc41f8e 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -15,152 +15,169 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.media.cts"
-    android:targetSandboxVersion="2">
+     package="android.media.cts"
+     android:targetSandboxVersion="2">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER" />
-    <uses-permission android:name="android.permission.SET_MEDIA_KEY_LISTENER" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"/>
+    <uses-permission android:name="android.permission.SET_MEDIA_KEY_LISTENER"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
-    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.VIBRATE"/>
 
-    <permission android:name="android.media.cts" android:protectionLevel="normal" />
+    <permission android:name="android.media.cts"
+         android:protectionLevel="normal"/>
 
-    <application
-        android:networkSecurityConfig="@xml/network_security_config"
-        android:requestLegacyExternalStorage="true"
-        android:largeHeap="true">
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+    <application android:networkSecurityConfig="@xml/network_security_config"
+         android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
 
         <activity android:name="android.media.cts.MediaProjectionActivity"
-            android:label="MediaProjectionActivity"
-            android:screenOrientation="locked"/>
+             android:label="MediaProjectionActivity"
+             android:screenOrientation="locked"/>
         <activity android:name="android.media.cts.AudioManagerStub"
-            android:label="AudioManagerStub"/>
+             android:label="AudioManagerStub"/>
         <activity android:name="android.media.cts.AudioManagerStubHelper"
-            android:label="AudioManagerStubHelper"/>
+             android:label="AudioManagerStubHelper"/>
         <activity android:name="android.media.cts.DecodeAccuracyTestActivity"
-            android:label="DecodeAccuracyTestActivity"
-            android:screenOrientation="locked"
-            android:configChanges="mcc|mnc|keyboard|keyboardHidden|orientation|screenSize|navigation">
+             android:label="DecodeAccuracyTestActivity"
+             android:screenOrientation="locked"
+             android:configChanges="mcc|mnc|keyboard|keyboardHidden|orientation|screenSize|navigation"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.MediaSessionTestActivity"
-                  android:label="MediaSessionTestActivity"
-                  android:screenOrientation="nosensor"
-                  android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
+             android:label="MediaSessionTestActivity"
+             android:screenOrientation="nosensor"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.MediaStubActivity"
-            android:label="MediaStubActivity"
-            android:screenOrientation="nosensor"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
+             android:label="MediaStubActivity"
+             android:screenOrientation="nosensor"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.MediaStubActivity2"
-            android:label="MediaStubActivity2"
-            android:screenOrientation="nosensor"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
+             android:label="MediaStubActivity2"
+             android:screenOrientation="nosensor"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.FaceDetectorStub"
-            android:label="FaceDetectorStub"/>
+             android:label="FaceDetectorStub"/>
         <activity android:name="android.media.cts.MediaPlayerSurfaceStubActivity"
-            android:label="MediaPlayerSurfaceStubActivity">
+             android:label="MediaPlayerSurfaceStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.ResourceManagerStubActivity"
-            android:label="ResourceManagerStubActivity">
+             android:label="ResourceManagerStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.ResourceManagerTestActivity1"
-            android:label="ResourceManagerTestActivity1"
-            android:process=":mediaCodecTestProcess1">
+             android:label="ResourceManagerTestActivity1"
+             android:process=":mediaCodecTestProcess1">
         </activity>
         <activity android:name="android.media.cts.ResourceManagerTestActivity2"
-            android:label="ResourceManagerTestActivity2"
-            android:process=":mediaCodecTestProcess2">
+             android:label="ResourceManagerTestActivity2"
+             android:process=":mediaCodecTestProcess2">
         </activity>
         <activity android:name="android.media.cts.RingtonePickerActivity"
-            android:label="RingtonePickerActivity">
+             android:label="RingtonePickerActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.media.cts.MockActivity" />
+        <activity android:name="android.media.cts.MockActivity"/>
         <service android:name="android.media.cts.RemoteVirtualDisplayService"
-            android:process=":remoteService" >
+             android:process=":remoteService"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </service>
-        <service android:name="android.media.cts.StubMediaBrowserService">
+        <service android:name="android.media.cts.StubMediaBrowserService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService" />
+                <action android:name="android.media.browse.MediaBrowserService"/>
             </intent-filter>
         </service>
         <service android:name="android.media.cts.StubMediaSession2Service"
-            android:permission="android.media.cts">
+             android:permission="android.media.cts"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.MediaSession2Service" />
+                <action android:name="android.media.MediaSession2Service"/>
             </intent-filter>
         </service>
         <service android:name="android.media.cts.LocalMediaProjectionService"
-            android:foregroundServiceType="mediaProjection"
-            android:enabled="true">
+             android:foregroundServiceType="mediaProjection"
+             android:enabled="true">
         </service>
         <service android:name=".StubMediaRoute2ProviderService"
-            android:exported="true">
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.MediaRoute2ProviderService" />
+                <action android:name="android.media.MediaRoute2ProviderService"/>
             </intent-filter>
-       </service>
-       <receiver android:name="android.media.cts.MediaButtonReceiver" />
+        </service>
+        <service android:name="android.media.cts.MediaButtonReceiverService"
+             android:exported="true"/>
+        <service android:name=".MediaBrowserServiceTestService"
+             android:process=":mediaBrowserServiceTestService"/>
+        <service android:name=".MediaSessionTestService"
+             android:process=":mediaSessionTestService"/>
+        <receiver android:name="android.media.cts.MediaButtonBroadcastReceiver"/>
     </application>
 
-    <uses-sdk android:minSdkVersion="29"   android:targetSdkVersion="29" />
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.media.cts"
-                     android:label="CTS tests of android.media">
+         android:targetPackage="android.media.cts"
+         android:label="CTS tests of android.media">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/media/aidl/android/media/cts/IRemoteService.aidl b/tests/tests/media/aidl/android/media/cts/IRemoteService.aidl
new file mode 100644
index 0000000..5aacc30
--- /dev/null
+++ b/tests/tests/media/aidl/android/media/cts/IRemoteService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.os.Bundle;
+
+interface IRemoteService {
+    boolean run(int testId, int step, in Bundle args);
+}
diff --git a/tests/tests/media/res/raw/jpeg_with_datetime_tag.jpg b/tests/tests/media/res/raw/jpeg_with_datetime_tag.jpg
new file mode 100644
index 0000000..f50e845
--- /dev/null
+++ b/tests/tests/media/res/raw/jpeg_with_datetime_tag.jpg
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
index c3a3e68..f043f93 100644
--- a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
+++ b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
@@ -467,6 +467,9 @@
                             warn(mDecoder.getWarnings());
                             mDecoder.clearWarnings();
                             mDecoder.flush();
+                            // First run will trigger output format change exactly once,
+                            // and subsequent runs should not trigger format change.
+                            assertEquals(1, mDecoder.getOutputFormatChangeCount());
                         }
                     });
                 if (verify) {
@@ -596,7 +599,10 @@
                                     segmentSize,
                                     lastSequence /* sendEos */,
                                     lastSequence /* expectEos */,
-                                    mAdjustTimeUs);
+                                    mAdjustTimeUs,
+                                    // Try sleeping after first queue so that we can verify
+                                    // output format change event happens at the right time.
+                                    true /* sleepAfterFirstQueue */);
                             if (lastSequence && frames >= 0) {
                                 warn("did not receive EOS, received " + frames + " frames");
                             } else if (!lastSequence && frames < 0) {
@@ -676,7 +682,8 @@
                                 framesBeforeEos,
                                 true /* sendEos */,
                                 true /* expectEos */,
-                                adjustTimeUs);
+                                adjustTimeUs,
+                                false /* sleepAfterFirstQueue */);
                         if (framesB >= 0) {
                             warn("did not receive EOS, received " + (-framesB) + " frames");
                         }
@@ -750,7 +757,8 @@
                                 segmentSize,
                                 lastSequence /* sendEos */,
                                 lastSequence /* expectEos */,
-                                mAdjustTimeUs);
+                                mAdjustTimeUs,
+                                false /* sleepAfterFirstQueue */);
                             if (lastSequence && frames >= 0) {
                                 warn("did not receive EOS, received " + frames + " frames");
                             } else if (!lastSequence && frames < 0) {
@@ -878,6 +886,13 @@
         Vector<Long> mRenderedTimeStamps; // using Vector as it is implicitly synchronized
         long mLastRenderNanoTime;
         int mFramesNotifiedRendered;
+        // True iff previous dequeue request returned INFO_OUTPUT_FORMAT_CHANGED.
+        boolean mOutputFormatChanged;
+        // Number of output format change event
+        int mOutputFormatChangeCount;
+        // Save the timestamps of the first frame of each sequence.
+        // Note: this is the only time output format change could happen.
+        ArrayList<Long> mFirstQueueTimestamps;
 
         public Decoder(String codecName) {
             MediaCodec codec = null;
@@ -895,6 +910,9 @@
             mRenderedTimeStamps = new Vector<Long>();
             mLastRenderNanoTime = System.nanoTime();
             mFramesNotifiedRendered = 0;
+            mOutputFormatChanged = false;
+            mOutputFormatChangeCount = 0;
+            mFirstQueueTimestamps = new ArrayList<Long>();
 
             codec.setOnFrameRenderedListener(this, null);
         }
@@ -928,6 +946,10 @@
             mWarnings.clear();
         }
 
+        public int getOutputFormatChangeCount() {
+            return mOutputFormatChangeCount;
+        }
+
         public void configureAndStart(MediaFormat format, TestSurface surface) {
             mSurface = surface;
             Log.i(TAG, "configure(" + format + ", " + mSurface.getSurface() + ")");
@@ -984,6 +1006,8 @@
                 Log.d(TAG, "output format has changed to " + format);
                 int colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
                 mDoChecksum = isRecognizedFormat(colorFormat);
+                mOutputFormatChanged = true;
+                ++mOutputFormatChangeCount;
                 return null;
             } else if (ix < 0) {
                 Log.v(TAG, "no output");
@@ -992,7 +1016,6 @@
             /* create checksum */
             long sum = 0;
 
-
             Log.v(TAG, "dequeue #" + ix + " => { [" + info.size + "] flags=" + info.flags +
                     " @" + info.presentationTimeUs + "}");
 
@@ -1027,6 +1050,16 @@
                 }
             }
 
+            if (mOutputFormatChanged) {
+                // Previous dequeue was output format change; format change must
+                // correspond to a new sequence, so it must happen right before
+                // the first frame of one of the sequences.
+                assertTrue("cannot find " + info.presentationTimeUs +
+                        " in " + mFirstQueueTimestamps,
+                        mFirstQueueTimestamps.remove(info.presentationTimeUs));
+                mOutputFormatChanged = false;
+            }
+
             return String.format(Locale.US, "{pts=%d, flags=%x, data=0x%x}",
                                  info.presentationTimeUs, info.flags, sum);
         }
@@ -1076,7 +1109,8 @@
         public int queueInputBufferRange(
                 Media media, int frameStartIx, int frameEndIx, boolean sendEosAtEnd,
                 boolean waitForEos) {
-            return queueInputBufferRange(media,frameStartIx,frameEndIx,sendEosAtEnd,waitForEos,0);
+            return queueInputBufferRange(
+                    media, frameStartIx, frameEndIx, sendEosAtEnd, waitForEos, 0, false);
         }
 
         public void queueCSD(MediaFormat format) {
@@ -1106,7 +1140,7 @@
 
         public int queueInputBufferRange(
                 Media media, int frameStartIx, int frameEndIx, boolean sendEosAtEnd,
-                boolean waitForEos, long adjustTimeUs) {
+                boolean waitForEos, long adjustTimeUs, boolean sleepAfterFirstQueue) {
             MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
             int frameIx = frameStartIx;
             int numFramesDecoded = 0;
@@ -1122,6 +1156,19 @@
                             frameIx,
                             sendEosAtEnd && (frameIx + 1 == frameEndIx),
                             adjustTimeUs)) {
+                        if (frameIx == frameStartIx) {
+                            if (sleepAfterFirstQueue) {
+                                // MediaCodec detects and processes output format change upon
+                                // the first frame. It must not send the event prematurely with
+                                // pending buffers to be dequeued. Sleep after the first frame
+                                // with new resolution to make sure MediaCodec had enough time
+                                // to process the frame with pending buffers.
+                                try {
+                                    Thread.sleep(100);
+                                } catch (InterruptedException e) {}
+                            }
+                            mFirstQueueTimestamps.add(mTimeStamps.get(mTimeStamps.size() - 1));
+                        }
                         frameIx++;
                     }
                 }
diff --git a/tests/tests/media/src/android/media/cts/AudioHelper.java b/tests/tests/media/src/android/media/cts/AudioHelper.java
index 12c6e64..ccd317d 100644
--- a/tests/tests/media/src/android/media/cts/AudioHelper.java
+++ b/tests/tests/media/src/android/media/cts/AudioHelper.java
@@ -272,6 +272,7 @@
 
         private final String mTag;
         private final int mSampleRate;
+        private final long mStartFrames; // initial timestamp condition for verification.
 
         // Running statistics
         private int mCount = 0;
@@ -284,9 +285,10 @@
         private int mWarmupCount = 0;
 
         public TimestampVerifier(@Nullable String tag, @IntRange(from=4000) int sampleRate,
-                boolean isProAudioDevice) {
+                                 long startFrames, boolean isProAudioDevice) {
             mTag = tag;  // Log accepts null
             mSampleRate = sampleRate;
+            mStartFrames = startFrames;
             // Warning if higher than MUST value for pro audio.  Zero means ignore.
             TEST_STARTUP_TIME_MS_WARN = isProAudioDevice ? 200. : 0.;
         }
@@ -296,14 +298,14 @@
         public double getStdJitterMs() { return Math.sqrt(mSecondMomentJitterMs / mJitterCount); }
         public double getMaxAbsJitterMs() { return mMaxAbsJitterMs; }
         public double getStartTimeNs() {
-            return mLastTimeNs - (mLastFrames * NANOS_PER_SECOND / mSampleRate);
+            return mLastTimeNs - ((mLastFrames - mStartFrames) * NANOS_PER_SECOND / mSampleRate);
         }
 
         public void add(@NonNull AudioTimestamp ts) {
             final long frames = ts.framePosition;
             final long timeNs = ts.nanoTime;
 
-            assertTrue("timestamps must have causal time", System.nanoTime() >= timeNs);
+            assertTrue(mTag + " timestamps must have causal time", System.nanoTime() >= timeNs);
 
             if (mCount > 0) { // need delta info from previous iteration (skipping first)
                 final long deltaFrames = frames - mLastFrames;
@@ -322,8 +324,8 @@
                         + ") deltaFrames(" + deltaFrames
                         + ") deltaTimeNs(" + deltaTimeNs
                         + ") jitterMs(" + jitterMs + ")");
-                assertTrue("timestamp time should be increasing", deltaTimeNs >= 0);
-                assertTrue("timestamp frames should be increasing", deltaFrames >= 0);
+                assertTrue(mTag + " timestamp time should be increasing", deltaTimeNs >= 0);
+                assertTrue(mTag + " timestamp frames should be increasing", deltaFrames >= 0);
 
                 if (mLastFrames != 0) {
                     if (mWarmupCount++ > 1) { // ensure device is warmed up
@@ -350,7 +352,7 @@
 
         public void verifyAndLog(long trackStartTimeNs, @Nullable String logName) {
             // enough timestamps?
-            assertTrue("need at least 2 jitter measurements", mJitterCount >= 2);
+            assertTrue(mTag + " need at least 2 jitter measurements", mJitterCount >= 2);
 
             // Compute startup time and std jitter.
             final int startupTimeMs =
@@ -358,7 +360,7 @@
             final double stdJitterMs = getStdJitterMs();
 
             // Check startup time
-            assertTrue("expect startupTimeMs " + startupTimeMs
+            assertTrue(mTag + " expect startupTimeMs " + startupTimeMs
                             + " <= " + TEST_STARTUP_TIME_MS_ALLOWED,
                     startupTimeMs <= TEST_STARTUP_TIME_MS_ALLOWED);
             if (TEST_STARTUP_TIME_MS_WARN > 0 && startupTimeMs > TEST_STARTUP_TIME_MS_WARN) {
@@ -370,7 +372,7 @@
             }
 
             // Check maximum jitter
-            assertTrue("expect maxAbsJitterMs(" + mMaxAbsJitterMs + ") < "
+            assertTrue(mTag + " expect maxAbsJitterMs(" + mMaxAbsJitterMs + ") < "
                             + TEST_MAX_JITTER_MS_ALLOWED,
                     mMaxAbsJitterMs < TEST_MAX_JITTER_MS_ALLOWED);
 
@@ -379,7 +381,8 @@
                 Log.w(mTag, "CDD warning: std timestamp jitter " + stdJitterMs
                         + " > " + TEST_STD_JITTER_MS_WARN);
             }
-            assertTrue("expect stdJitterMs " + stdJitterMs + " < " + TEST_STD_JITTER_MS_ALLOWED,
+            assertTrue(mTag + " expect stdJitterMs " + stdJitterMs +
+                            " < " + TEST_STD_JITTER_MS_ALLOWED,
                     stdJitterMs < TEST_STD_JITTER_MS_ALLOWED);
 
             Log.d(mTag, "startupTimeMs(" + startupTimeMs
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 2d85462..50c63f2 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -16,6 +16,8 @@
 
 package android.media.cts;
 
+import static org.junit.Assert.assertNotEquals;
+
 import static android.media.AudioManager.ADJUST_LOWER;
 import static android.media.AudioManager.ADJUST_RAISE;
 import static android.media.AudioManager.ADJUST_SAME;
@@ -27,8 +29,13 @@
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
 import static android.media.AudioManager.STREAM_ACCESSIBILITY;
+import static android.media.AudioManager.STREAM_ALARM;
+import static android.media.AudioManager.STREAM_DTMF;
 import static android.media.AudioManager.STREAM_MUSIC;
+import static android.media.AudioManager.STREAM_NOTIFICATION;
 import static android.media.AudioManager.STREAM_RING;
+import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.AudioManager.STREAM_VOICE_CALL;
 import static android.media.AudioManager.USE_DEFAULT_STREAM_TYPE;
 import static android.media.AudioManager.VIBRATE_SETTING_OFF;
 import static android.media.AudioManager.VIBRATE_SETTING_ON;
@@ -37,7 +44,6 @@
 import static android.media.AudioManager.VIBRATE_TYPE_RINGER;
 import static android.provider.Settings.System.SOUND_EFFECTS_ENABLED;
 
-import android.app.INotificationManager;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
@@ -47,11 +53,15 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.MediaPlayer;
+import android.media.MediaRecorder;
 import android.media.MicrophoneInfo;
-import android.os.ServiceManager;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Build;
+import android.os.SystemClock;
 import android.os.Vibrator;
 import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings;
@@ -61,21 +71,26 @@
 import android.util.Log;
 import android.view.SoundEffectConstants;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.MediaUtils;
 import com.android.internal.annotations.GuardedBy;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executors;
 
 @NonMediaMainlineTest
 public class AudioManagerTest extends InstrumentationTestCase {
     private final static String TAG = "AudioManagerTest";
 
     private final static long ASYNC_TIMING_TOLERANCE_MS = 50;
-    private final static int MP3_TO_PLAY = R.raw.testmp3;
+    private final static long POLL_TIME_VOLUME_ADJUST = 200;
+    private final static long POLL_TIME_UPDATE_INTERRUPTION_FILTER = 5000;
+    private final static int MP3_TO_PLAY = R.raw.testmp3; // ~ 5 second mp3
+    private final static long POLL_TIME_PLAY_MUSIC = 2000;
     private final static long TIME_TO_PLAY = 2000;
     private final static String APPOPS_OP_STR = "android:write_settings";
     private AudioManager mAudioManager;
@@ -121,14 +136,14 @@
 
         // Store the original volumes that that they can be recovered in tearDown().
         final int[] streamTypes = {
-            AudioManager.STREAM_VOICE_CALL,
-            AudioManager.STREAM_SYSTEM,
-            AudioManager.STREAM_RING,
-            AudioManager.STREAM_MUSIC,
-            AudioManager.STREAM_ALARM,
-            AudioManager.STREAM_NOTIFICATION,
-            AudioManager.STREAM_DTMF,
-            AudioManager.STREAM_ACCESSIBILITY,
+            STREAM_VOICE_CALL,
+            STREAM_SYSTEM,
+            STREAM_RING,
+            STREAM_MUSIC,
+            STREAM_ALARM,
+            STREAM_NOTIFICATION,
+            STREAM_DTMF,
+            STREAM_ACCESSIBILITY,
         };
         mOriginalRingerMode = mAudioManager.getRingerMode();
         for (int streamType : streamTypes) {
@@ -146,7 +161,7 @@
                     mContext.getPackageName(), getInstrumentation(), false);
         }
 
-        // Check original mirchrophone mute/unmute status
+        // Check original microphone mute/unmute status
         mDoNotCheckUnmute = false;
         if (mAudioManager.isMicrophoneMute()) {
             mAudioManager.setMicrophoneMute(false);
@@ -273,7 +288,7 @@
         // should hear sound after loadSoundEffects() called.
         mAudioManager.loadSoundEffects();
         Thread.sleep(TIME_TO_PLAY);
-        float volume = 13;
+        float volume = 0.5f;  // volume should be between 0.f to 1.f (or -1).
         mAudioManager.playSoundEffect(SoundEffectConstants.CLICK);
         mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
         mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
@@ -328,15 +343,12 @@
         }
         MediaPlayer mp = MediaPlayer.create(mContext, MP3_TO_PLAY);
         assertNotNull(mp);
-        mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+        mp.setAudioStreamType(STREAM_MUSIC);
         mp.start();
-        Thread.sleep(TIME_TO_PLAY);
-        assertTrue(mAudioManager.isMusicActive());
-        Thread.sleep(TIME_TO_PLAY);
+        assertMusicActive(true);
         mp.stop();
         mp.release();
-        Thread.sleep(TIME_TO_PLAY);
-        assertFalse(mAudioManager.isMusicActive());
+        assertMusicActive(false);
     }
 
     @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
@@ -609,13 +621,13 @@
         Utils.toggleNotificationPolicyAccess(
                 mContext.getPackageName(), getInstrumentation(), true);
         int volume, volumeDelta;
-        int[] streams = {AudioManager.STREAM_ALARM,
-                AudioManager.STREAM_MUSIC,
-                AudioManager.STREAM_VOICE_CALL,
-                AudioManager.STREAM_RING};
+        int[] streams = {STREAM_ALARM,
+                STREAM_MUSIC,
+                STREAM_VOICE_CALL,
+                STREAM_RING};
 
         mAudioManager.adjustVolume(ADJUST_RAISE, 0);
-        // adjusting volume is aynchronous, wait before other volume checks
+        // adjusting volume is asynchronous, wait before other volume checks
         Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
         mAudioManager.adjustSuggestedStreamVolume(
                 ADJUST_LOWER, USE_DEFAULT_STREAM_TYPE, 0);
@@ -623,8 +635,7 @@
         int maxMusicVolume = mAudioManager.getStreamMaxVolume(STREAM_MUSIC);
 
         for (int stream : streams) {
-
-            if (mIsSingleVolume && stream != AudioManager.STREAM_MUSIC) {
+            if (mIsSingleVolume && stream != STREAM_MUSIC) {
                 continue;
             }
 
@@ -649,7 +660,7 @@
             assertEquals(String.format("stream=%d", stream),
                     minNonZeroVolume, mAudioManager.getStreamVolume(stream));
 
-            if (stream == AudioManager.STREAM_MUSIC && mAudioManager.isWiredHeadsetOn()) {
+            if (stream == STREAM_MUSIC && mAudioManager.isWiredHeadsetOn()) {
                 // due to new regulations, music sent over a wired headset may be volume limited
                 // until the user explicitly increases the limit, so we can't rely on being able
                 // to set the volume to getStreamMaxVolume(). Instead, determine the current limit
@@ -670,10 +681,8 @@
 
             volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
             mAudioManager.adjustSuggestedStreamVolume(ADJUST_LOWER, stream, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            assertEquals("Vol ADJUST_LOWER suggested stream:" + stream + " maxVol:" + maxVolume
-                    + " volDelta:" + volumeDelta,
-                    maxVolume - volumeDelta, mAudioManager.getStreamVolume(stream));
+            assertStreamVolumeEquals(stream, maxVolume - volumeDelta,
+                    "Vol ADJUST_LOWER suggested stream:" + stream + " maxVol:" + maxVolume);
 
             // volume lower
             mAudioManager.setStreamVolume(stream, maxVolume, 0);
@@ -681,10 +690,9 @@
             while (volume > minVolume) {
                 volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
                 mAudioManager.adjustStreamVolume(stream, ADJUST_LOWER, 0);
-                assertEquals("Vol ADJUST_LOWER on stream:" + stream + " vol:" + volume
-                                + " minVol:" + minVolume + " volDelta:" + volumeDelta,
-                        Math.max(0, volume - volumeDelta),
-                        mAudioManager.getStreamVolume(stream));
+                assertStreamVolumeEquals(stream,  Math.max(0, volume - volumeDelta),
+                        "Vol ADJUST_LOWER on stream:" + stream + " vol:" + volume
+                                + " minVol:" + minVolume + " volDelta:" + volumeDelta);
                 volume = mAudioManager.getStreamVolume(stream);
             }
 
@@ -696,10 +704,9 @@
             while (volume < maxVolume) {
                 volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
                 mAudioManager.adjustStreamVolume(stream, ADJUST_RAISE, 0);
-                assertEquals("Vol ADJUST_RAISE on stream:" + stream + " vol:" + volume
-                                + " maxVol:" + maxVolume + " volDelta:" + volumeDelta,
-                        Math.min(volume + volumeDelta, maxVolume),
-                        mAudioManager.getStreamVolume(stream));
+                assertStreamVolumeEquals(stream,   Math.min(volume + volumeDelta, maxVolume),
+                        "Vol ADJUST_RAISE on stream:" + stream + " vol:" + volume
+                                + " maxVol:" + maxVolume + " volDelta:" + volumeDelta);
                 volume = mAudioManager.getStreamVolume(stream);
             }
 
@@ -732,38 +739,31 @@
         mp.setAudioStreamType(STREAM_MUSIC);
         mp.setLooping(true);
         mp.start();
-        Thread.sleep(TIME_TO_PLAY);
-        assertTrue(mAudioManager.isMusicActive());
+        assertMusicActive(true);
 
         // adjust volume as ADJUST_SAME
         for (int k = 0; k < maxMusicVolume; k++) {
             mAudioManager.adjustVolume(ADJUST_SAME, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            assertEquals(maxMusicVolume, mAudioManager.getStreamVolume(STREAM_MUSIC));
+            assertStreamVolumeEquals(STREAM_MUSIC, maxMusicVolume);
         }
 
         // adjust volume as ADJUST_RAISE
         mAudioManager.setStreamVolume(STREAM_MUSIC, 0, 0);
         volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
         mAudioManager.adjustVolume(ADJUST_RAISE, 0);
-        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-        assertEquals(Math.min(volumeDelta, maxMusicVolume),
-                mAudioManager.getStreamVolume(STREAM_MUSIC));
+        assertStreamVolumeEquals(STREAM_MUSIC, Math.min(volumeDelta, maxMusicVolume));
 
         // adjust volume as ADJUST_LOWER
         mAudioManager.setStreamVolume(STREAM_MUSIC, maxMusicVolume, 0);
         maxMusicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
         volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
         mAudioManager.adjustVolume(ADJUST_LOWER, 0);
-        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-        assertEquals(Math.max(0, maxMusicVolume - volumeDelta),
-                mAudioManager.getStreamVolume(STREAM_MUSIC));
+        assertStreamVolumeEquals(STREAM_MUSIC, Math.max(0, maxMusicVolume - volumeDelta));
 
         mp.stop();
         mp.release();
-        Thread.sleep(TIME_TO_PLAY);
         if (!isMusicPlayingBeforeTest) {
-            assertFalse(mAudioManager.isMusicActive());
+            assertMusicActive(false);
         }
     }
 
@@ -774,55 +774,50 @@
         }
         final int maxA11yVol = mAudioManager.getStreamMaxVolume(STREAM_ACCESSIBILITY);
         assertTrue("Max a11yVol not strictly positive", maxA11yVol > 0);
-        int currentVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
+        int originalVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
 
         // changing STREAM_ACCESSIBILITY is subject to permission, shouldn't be able to change it
         // test setStreamVolume
         final int testSetVol;
-        if (currentVol != maxA11yVol) {
+        if (originalVol != maxA11yVol) {
             testSetVol = maxA11yVol;
         } else {
             testSetVol = maxA11yVol - 1;
         }
         mAudioManager.setStreamVolume(STREAM_ACCESSIBILITY, testSetVol, 0);
-        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-        currentVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
-        assertTrue("Should not be able to change A11y vol", currentVol != testSetVol);
+        assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
+                "Should not be able to change A11y vol");
 
         // test adjustStreamVolume
         //        LOWER
-        currentVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
-        if (currentVol > 0) {
+        if (originalVol > 0) {
             mAudioManager.adjustStreamVolume(STREAM_ACCESSIBILITY, ADJUST_LOWER, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            int newVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
-            assertTrue("Should not be able to lower A11y vol", currentVol == newVol);
+            assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
+                    "Should not be able to change A11y vol");
         }
         //        RAISE
-        currentVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
-        if (currentVol < maxA11yVol) {
+        if (originalVol < maxA11yVol) {
             mAudioManager.adjustStreamVolume(STREAM_ACCESSIBILITY, ADJUST_RAISE, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            int newVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
-            assertTrue("Should not be able to raise A11y vol", currentVol == newVol);
+            assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
+                    "Should not be able to change A11y vol");
         }
     }
 
     public void testSetVoiceCallVolumeToZeroPermission() {
         // Verify that only apps with MODIFY_PHONE_STATE can set VOICE_CALL_STREAM to 0
-        mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, 0, 0);
+        mAudioManager.setStreamVolume(STREAM_VOICE_CALL, 0, 0);
         assertTrue("MODIFY_PHONE_STATE is required in order to set voice call volume to 0",
-                    mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL) != 0);
+                    mAudioManager.getStreamVolume(STREAM_VOICE_CALL) != 0);
     }
 
     public void testMuteFixedVolume() throws Exception {
         int[] streams = {
-                AudioManager.STREAM_VOICE_CALL,
-                AudioManager.STREAM_MUSIC,
-                AudioManager.STREAM_RING,
-                AudioManager.STREAM_ALARM,
-                AudioManager.STREAM_NOTIFICATION,
-                AudioManager.STREAM_SYSTEM};
+                STREAM_VOICE_CALL,
+                STREAM_MUSIC,
+                STREAM_RING,
+                STREAM_ALARM,
+                STREAM_NOTIFICATION,
+                STREAM_SYSTEM};
         if (mUseFixedVolume) {
             for (int stream : streams) {
                 mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
@@ -844,7 +839,7 @@
         if (mSkipRingerTests) {
             return;
         }
-        int[] streams = { AudioManager.STREAM_RING };
+        int[] streams = { STREAM_RING };
         // Mute streams
         Utils.toggleNotificationPolicyAccess(
                 mContext.getPackageName(), getInstrumentation(), true);
@@ -919,19 +914,19 @@
             return;
         }
         int[] streams = {
-                AudioManager.STREAM_VOICE_CALL,
-                AudioManager.STREAM_MUSIC,
-                AudioManager.STREAM_ALARM
+                STREAM_VOICE_CALL,
+                STREAM_MUSIC,
+                STREAM_ALARM
         };
 
         int muteAffectedStreams = System.getInt(mContext.getContentResolver(),
                 System.MUTE_STREAMS_AFFECTED,
                 // same defaults as in AudioService. Should be kept in sync.
                  (1 << STREAM_MUSIC) |
-                         (1 << AudioManager.STREAM_RING) |
-                         (1 << AudioManager.STREAM_NOTIFICATION) |
-                         (1 << AudioManager.STREAM_SYSTEM) |
-                         (1 << AudioManager.STREAM_VOICE_CALL));
+                         (1 << STREAM_RING) |
+                         (1 << STREAM_NOTIFICATION) |
+                         (1 << STREAM_SYSTEM) |
+                         (1 << STREAM_VOICE_CALL));
 
         Utils.toggleNotificationPolicyAccess(
                 mContext.getPackageName(), getInstrumentation(), true);
@@ -963,7 +958,7 @@
 
     private void testStreamMuting(int stream) {
         // Voice call requires MODIFY_PHONE_STATE, so we should not be able to mute
-        if (stream == AudioManager.STREAM_VOICE_CALL) {
+        if (stream == STREAM_VOICE_CALL) {
             mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
             assertFalse("Muting voice call stream (" + stream + ") should require "
                             + "MODIFY_PHONE_STATE.", mAudioManager.isStreamMute(stream));
@@ -1015,14 +1010,13 @@
         try {
             Utils.toggleNotificationPolicyAccess(
                     mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
             mAudioManager.adjustStreamVolume(
-                    AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+                    STREAM_MUSIC, ADJUST_RAISE, 0);
+            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume);
 
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
@@ -1036,18 +1030,16 @@
         try {
             Utils.toggleNotificationPolicyAccess(
                     mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
 
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS);
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
             mAudioManager.adjustStreamVolume(
-                    AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+                    STREAM_MUSIC, ADJUST_RAISE, 0);
             int volumeDelta =
-                    getVolumeDelta(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
-            assertEquals(musicVolume + volumeDelta,
-                    mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+                    getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
+            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume + volumeDelta);
 
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
@@ -1061,18 +1053,19 @@
         try {
             Utils.toggleNotificationPolicyAccess(
                     mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
 
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
-            // delay for streams to get into correct volume states
+            // delay for streams interruption filter to get into correct state
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 7, 0);
-            assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 7, 0);
-            assertEquals(7, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+            // cannot adjust music, can adjust ringer since it could exit DND
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 7, 0);
+            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume);
+            mAudioManager.setStreamVolume(STREAM_RING, 7, 0);
+            assertStreamVolumeEquals(STREAM_RING, 7);
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1085,16 +1078,17 @@
         try {
             Utils.toggleNotificationPolicyAccess(
                     mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS);
             // delay for streams to get into correct volume states
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 3, 0);
-            assertEquals(3, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 7, 0);
-            assertEquals(7, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+            // can still adjust music and ring volume
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 3, 0);
+            assertStreamVolumeEquals(STREAM_MUSIC, 3);
+            mAudioManager.setStreamVolume(STREAM_RING, 7, 0);
+            assertStreamVolumeEquals(STREAM_RING, 7);
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1112,27 +1106,27 @@
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
 
             final int testRingerVol = getTestRingerVol();
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-            int alarmVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+            int alarmVolume = mAudioManager.getStreamVolume(STREAM_ALARM);
 
             // disallow all sounds in priority only, turn on priority only DND, try to change volume
             mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0 , 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
             // delay for streams to get into correct volume states
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 3, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 5, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, testRingerVol, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 3, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 5, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, testRingerVol, 0);
 
             // Turn off zen and make sure stream levels are still the same prior to zen
             // aside from ringer since ringer can exit dnd
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS); // delay for streams to get into correct states
-            assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
-            assertEquals(alarmVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM));
-            assertEquals(testRingerVol, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+            assertEquals(musicVolume, mAudioManager.getStreamVolume(STREAM_MUSIC));
+            assertEquals(alarmVolume, mAudioManager.getStreamVolume(STREAM_ALARM));
+            assertEquals(testRingerVol, mAudioManager.getStreamVolume(STREAM_RING));
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1148,12 +1142,12 @@
         try {
             // turn off zen, set stream volumes to check for later
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            int ringVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_RING);
-            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-            int alarmVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            int ringVolume = mAudioManager.getStreamVolume(STREAM_RING);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+            int alarmVolume = mAudioManager.getStreamVolume(STREAM_ALARM);
 
             // disallow all sounds in priority only, turn on priority only DND, try to change volume
             mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
@@ -1161,23 +1155,23 @@
             // delay for streams to get into correct mute states
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
             mAudioManager.adjustStreamVolume(
-                    AudioManager.STREAM_RING, AudioManager.ADJUST_RAISE, 0);
+                    STREAM_RING, ADJUST_RAISE, 0);
             mAudioManager.adjustStreamVolume(
-                    AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
+                    STREAM_MUSIC, ADJUST_RAISE, 0);
             mAudioManager.adjustStreamVolume(
-                    AudioManager.STREAM_ALARM, AudioManager.ADJUST_RAISE, 0);
+                    STREAM_ALARM, ADJUST_RAISE, 0);
 
             // Turn off zen and make sure stream levels are still the same prior to zen
             // aside from ringer since ringer can exit dnd
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS); // delay for streams to get into correct states
-            assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
-            assertEquals(alarmVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM));
+            assertEquals(musicVolume, mAudioManager.getStreamVolume(STREAM_MUSIC));
+            assertEquals(alarmVolume, mAudioManager.getStreamVolume(STREAM_ALARM));
 
             int volumeDelta =
-                    getVolumeDelta(mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+                    getVolumeDelta(mAudioManager.getStreamVolume(STREAM_RING));
             assertEquals(ringVolume + volumeDelta,
-                    mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+                    mAudioManager.getStreamVolume(STREAM_RING));
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1192,33 +1186,26 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // disallow all sounds in priority only, turn on priority only DND
             mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
 
             // if channels cannot bypass DND, the Ringer stream should be muted, else it
             // shouldn't be muted
-            if (!mAppsBypassingDnd) {
-                assertTrue("Ringer stream should be muted",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            } else {
-                assertFalse("Ringer stream shouldn't be muted b/c channels can bypass DND",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            }
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if channels cannot bypassDnd");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1232,34 +1219,27 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // allow only media in priority only
             mNm.setNotificationPolicy(new NotificationManager.Policy(
                     NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertFalse("Music (media) stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+            assertStreamMuted(STREAM_MUSIC, false,
+                    "Music (media) stream should not be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
 
             // if channels cannot bypass DND, the Ringer stream should be muted, else it
             // shouldn't be muted
-            if (!mAppsBypassingDnd) {
-                assertTrue("Ringer stream should be muted",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            } else {
-                assertFalse("Ringer stream shouldn't be muted b/c channels can bypass DND",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            }
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if channels cannot bypassDnd");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1274,26 +1254,24 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // allow only system in priority only
             mNm.setNotificationPolicy(new NotificationManager.Policy(
                     NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertFalse("System stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
-            assertFalse("Ringer stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, false,
+                    "System stream should not be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, false,
+                    "Ringer stream should not be muted");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1308,27 +1286,25 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test, but then mute ringer
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 0, 0);
             mAudioManager.setRingerMode(RINGER_MODE_SILENT);
 
             // allow only system in priority only
             mNm.setNotificationPolicy(new NotificationManager.Policy(
                     NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
-           assertTrue("Ringer stream should be muted",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, true,
+                    "Ringer stream should be muted");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1343,34 +1319,27 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // allow only alarms in priority only
             mNm.setNotificationPolicy(new NotificationManager.Policy(
                     NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertFalse("Alarm stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, false,
+                    "Alarm stream should not be muted");
 
             // if channels cannot bypass DND, the Ringer stream should be muted, else it
             // shouldn't be muted
-            if (!mAppsBypassingDnd) {
-                assertTrue("Ringer stream should be muted",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            } else {
-                assertFalse("Ringer stream shouldn't be muted b/c channels can bypass DND",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            }
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if channels cannot bypassDnd");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1385,48 +1354,46 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // allow only reminders in priority only
             mNm.setNotificationPolicy(new NotificationManager.Policy(
                     NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
-            assertFalse("Ringer stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, false,
+                    "Ringer stream should not be muted");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
     }
 
     public void testPriorityOnlyChannelsCanBypassDnd() throws Exception {
-        final String NOTIFICATION_CHANNEL_ID = "test_id";
         if (mSkipRingerTests) {
             return;
         }
 
         Utils.toggleNotificationPolicyAccess(
                 mContext.getPackageName(), getInstrumentation(), true);
+
+        final String NOTIFICATION_CHANNEL_ID = "test_id_" + SystemClock.uptimeMillis();
         NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "TEST",
                 NotificationManager.IMPORTANCE_DEFAULT);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // create a channel that can bypass dnd
             channel.setBypassDnd(true);
@@ -1435,39 +1402,33 @@
             // allow nothing
             mNm.setNotificationPolicy(new NotificationManager.Policy(0,0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
-            assertFalse("Ringer stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, false,
+                    "Ringer stream should not be muted."
+                            + " areChannelsBypassing="
+                            + NotificationManager.getService().areChannelsBypassingDnd());
 
             // delete the channel that can bypass dnd
             mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should still be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should still be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should still be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
-
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
             // if channels cannot bypass DND, the Ringer stream should be muted, else it
             // shouldn't be muted
-            if (!mAppsBypassingDnd) {
-                assertTrue("Ringer stream should be muted",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            } else {
-                assertFalse("Ringer stream shouldn't be muted b/c channels can bypass DND",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            }
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if apps are bypassing dnd"
+                            + " areChannelsBypassing="
+                            + NotificationManager.getService().areChannelsBypassingDnd());
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
             mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
@@ -1481,10 +1442,10 @@
         mAudioManager.adjustVolume(37, 0);
     }
 
-    private final int[] PUBLIC_STREAM_TYPES = { AudioManager.STREAM_VOICE_CALL,
-            AudioManager.STREAM_SYSTEM, AudioManager.STREAM_RING, AudioManager.STREAM_MUSIC,
-            AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION,
-            AudioManager.STREAM_DTMF,  AudioManager.STREAM_ACCESSIBILITY };
+    private final int[] PUBLIC_STREAM_TYPES = { STREAM_VOICE_CALL,
+            STREAM_SYSTEM, STREAM_RING, STREAM_MUSIC,
+            STREAM_ALARM, STREAM_NOTIFICATION,
+            STREAM_DTMF,  STREAM_ACCESSIBILITY };
 
     public void testGetStreamVolumeDbWithIllegalArguments() throws Exception {
         Exception ex = null;
@@ -1502,7 +1463,7 @@
         // invalid volume index
         ex = null;
         try {
-            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, -101 /*volume*/,
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, -101 /*volume*/,
                     AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
         } catch (Exception e) {
             ex = e; // expected
@@ -1514,8 +1475,8 @@
         // invalid out of range volume index
         ex = null;
         try {
-            final int maxVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, maxVol + 1,
+            final int maxVol = mAudioManager.getStreamMaxVolume(STREAM_MUSIC);
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, maxVol + 1,
                     AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
         } catch (Exception e) {
             ex = e; // expected
@@ -1527,7 +1488,7 @@
         // invalid device type
         ex = null;
         try {
-            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, 0,
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, 0,
                     -102 /*deviceType*/);
         } catch (Exception e) {
             ex = e; // expected
@@ -1539,7 +1500,7 @@
         // invalid input device type
         ex = null;
         try {
-            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, 0,
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, 0,
                     AudioDeviceInfo.TYPE_BUILTIN_MIC);
         } catch (Exception e) {
             ex = e; // expected
@@ -1570,10 +1531,10 @@
 
     public void testAdjustSuggestedStreamVolumeWithIllegalArguments() throws Exception {
         // Call the method with illegal direction. System should not reboot.
-        mAudioManager.adjustSuggestedStreamVolume(37, AudioManager.STREAM_MUSIC, 0);
+        mAudioManager.adjustSuggestedStreamVolume(37, STREAM_MUSIC, 0);
 
         // Call the method with illegal stream. System should not reboot.
-        mAudioManager.adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE, 66747, 0);
+        mAudioManager.adjustSuggestedStreamVolume(ADJUST_RAISE, 66747, 0);
     }
 
     @CddTest(requirement="5.4.1/C-1-4")
@@ -1608,19 +1569,42 @@
         }
     }
 
-    public void testIsHapticPlaybackSupported() throws Exception {
+    public void testIsHapticPlaybackSupported() {
         // Calling the API to make sure it doesn't crash.
         Log.i(TAG, "isHapticPlaybackSupported: " + AudioManager.isHapticPlaybackSupported());
     }
 
-    private void setInterruptionFilter(int filter) throws Exception {
-        mNm.setInterruptionFilter(filter);
-        for (int i = 0; i < 5; i++) {
-            if (mNm.getCurrentInterruptionFilter() == filter) {
-                break;
-            }
-            Thread.sleep(1000);
+    public void testGetAudioHwSyncForSession() {
+        // AudioManager.getAudioHwSyncForSession is not supported before S
+        if (ApiLevelUtil.isAtMost(Build.VERSION_CODES.R)) {
+            Log.i(TAG, "testGetAudioHwSyncForSession skipped, release: " + Build.VERSION.SDK_INT);
+            return;
         }
+        try {
+            int sessionId = mAudioManager.generateAudioSessionId();
+            assertNotEquals("testGetAudioHwSyncForSession cannot get audio session ID",
+                    AudioManager.ERROR, sessionId);
+            int hwSyncId = mAudioManager.getAudioHwSyncForSession(sessionId);
+            Log.i(TAG, "getAudioHwSyncForSession: " + hwSyncId);
+        } catch (UnsupportedOperationException e) {
+            Log.i(TAG, "getAudioHwSyncForSession not supported");
+        } catch (Exception e) {
+            fail("Unexpected exception thrown by getAudioHwSyncForSession: " + e);
+        }
+    }
+
+    private void setInterruptionFilter(int filter) {
+        mNm.setInterruptionFilter(filter);
+        final long startPoll = SystemClock.uptimeMillis();
+        int currentFilter = -1;
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_UPDATE_INTERRUPTION_FILTER) {
+            currentFilter = mNm.getCurrentInterruptionFilter();
+            if (currentFilter == filter) {
+                return;
+            }
+        }
+        Log.e(TAG, "interruption filter unsuccessfully set. wanted=" + filter
+                + " actual=" + currentFilter);
     }
 
     private int getVolumeDelta(int volume) {
@@ -1684,6 +1668,159 @@
         }
     }
 
+    static class MyPrevDevForStrategyListener implements
+            AudioManager.OnPreferredDevicesForStrategyChangedListener {
+        @Override
+        public void onPreferredDevicesForStrategyChanged(AudioProductStrategy strategy,
+                List<AudioDeviceAttributes> devices) {
+            fail("onPreferredDevicesForStrategyChanged must not be called");
+        }
+    }
+
+    public void testPreferredDevicesForStrategy() {
+        // setPreferredDeviceForStrategy
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        if (devices.length <= 0) {
+            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no output device");
+            return;
+        }
+        final AudioDeviceAttributes ada = new AudioDeviceAttributes(devices[0]);
+
+        final AudioAttributes mediaAttr = new AudioAttributes.Builder().setUsage(
+                AudioAttributes.USAGE_MEDIA).build();
+        final List<AudioProductStrategy> strategies =
+                AudioProductStrategy.getAudioProductStrategies();
+        AudioProductStrategy strategyForMedia = null;
+        for (AudioProductStrategy strategy : strategies) {
+            if (strategy.supportsAudioAttributes(mediaAttr)) {
+                strategyForMedia = strategy;
+                break;
+            }
+        }
+        if (strategyForMedia == null) {
+            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no strategy for media");
+            return;
+        }
+
+        try {
+            mAudioManager.setPreferredDeviceForStrategy(strategyForMedia, ada);
+            fail("setPreferredDeviceForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.getPreferredDeviceForStrategy(strategyForMedia);
+            fail("getPreferredDeviceForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        final List<AudioDeviceAttributes> adas = new ArrayList<>();
+        adas.add(ada);
+        try {
+            mAudioManager.setPreferredDevicesForStrategy(strategyForMedia, adas);
+            fail("setPreferredDevicesForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.getPreferredDevicesForStrategy(strategyForMedia);
+            fail("getPreferredDevicesForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        MyPrevDevForStrategyListener listener = new MyPrevDevForStrategyListener();
+        try {
+            mAudioManager.addOnPreferredDevicesForStrategyChangedListener(
+                    Executors.newSingleThreadExecutor(), listener);
+            fail("addOnPreferredDevicesForStrategyChangedListener must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        // There is not listener added at server side. Nothing to remove.
+        mAudioManager.removeOnPreferredDevicesForStrategyChangedListener(listener);
+    }
+
+    static class MyPrevDevicesForCapturePresetChangedListener implements
+            AudioManager.OnPreferredDevicesForCapturePresetChangedListener {
+        @Override
+        public void onPreferredDevicesForCapturePresetChanged(
+                int capturePreset, List<AudioDeviceAttributes> devices) {
+            fail("onPreferredDevicesForCapturePresetChanged must not be called");
+        }
+    }
+
+    public void testPreferredDeviceForCapturePreset() {
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+        if (devices.length <= 0) {
+            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no input device");
+            return;
+        }
+        final AudioDeviceAttributes ada = new AudioDeviceAttributes(devices[0]);
+
+        try {
+            mAudioManager.setPreferredDeviceForCapturePreset(MediaRecorder.AudioSource.MIC, ada);
+            fail("setPreferredDeviceForCapturePreset must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.getPreferredDevicesForCapturePreset(MediaRecorder.AudioSource.MIC);
+            fail("getPreferredDevicesForCapturePreset must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.clearPreferredDevicesForCapturePreset(MediaRecorder.AudioSource.MIC);
+            fail("clearPreferredDevicesForCapturePreset must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        MyPrevDevicesForCapturePresetChangedListener listener =
+                new MyPrevDevicesForCapturePresetChangedListener();
+        try {
+            mAudioManager.addOnPreferredDevicesForCapturePresetChangedListener(
+                Executors.newSingleThreadExecutor(), listener);
+            fail("addOnPreferredDevicesForCapturePresetChangedListener must fail"
+                    + "due to no permission");
+        } catch (SecurityException e) {
+        }
+        // There is not listener added at server side. Nothing to remove.
+        mAudioManager.removeOnPreferredDevicesForCapturePresetChangedListener(listener);
+    }
+
+    private void assertStreamVolumeEquals(int stream, int expectedVolume) throws Exception {
+        assertStreamVolumeEquals(stream, expectedVolume,
+                "Unexpected stream volume for stream=" + stream);
+    }
+
+    // volume adjustments are asynchronous, we poll the volume in case the volume state hasn't
+    // been adjusted yet
+    private void assertStreamVolumeEquals(int stream, int expectedVolume, String msg)
+            throws Exception {
+        final long startPoll = SystemClock.uptimeMillis();
+        int actualVolume = mAudioManager.getStreamVolume(stream);
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_VOLUME_ADJUST
+                && expectedVolume != actualVolume) {
+            actualVolume = mAudioManager.getStreamVolume(stream);
+        }
+        assertEquals(msg, expectedVolume, actualVolume);
+    }
+
+    // volume adjustments are asynchronous, we poll the volume in case the mute state hasn't
+    // changed yet
+    private void assertStreamMuted(int stream, boolean expectedMuteState, String msg)
+            throws Exception{
+        final long startPoll = SystemClock.uptimeMillis();
+        boolean actualMuteState = mAudioManager.isStreamMute(stream);
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_VOLUME_ADJUST
+                && expectedMuteState != actualMuteState) {
+            actualMuteState = mAudioManager.isStreamMute(stream);
+        }
+        assertEquals(msg, expectedMuteState, actualMuteState);
+    }
+
+    private void assertMusicActive(boolean expectedIsMusicActive) throws Exception {
+        final long startPoll = SystemClock.uptimeMillis();
+        boolean actualIsMusicActive = mAudioManager.isMusicActive();
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_PLAY_MUSIC
+                && expectedIsMusicActive != actualIsMusicActive) {
+            actualIsMusicActive = mAudioManager.isMusicActive();
+        }
+        assertEquals(actualIsMusicActive, actualIsMusicActive);
+    }
+
     // getParameters() & setParameters() are deprecated, so don't test
 
     // setAdditionalOutputDeviceDelay(), getAudioVolumeGroups(), getVolumeIndexForAttributes()
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index e14a6c5..2807f52 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -577,7 +577,7 @@
                 final short[] shortData = new short[BUFFER_SAMPLES];
                 final AudioHelper.TimestampVerifier tsVerifier =
                         new AudioHelper.TimestampVerifier(TAG, RECORD_SAMPLE_RATE,
-                                isProAudioDevice());
+                                0 /* startFrames */, isProAudioDevice());
 
                 while (samplesRead < targetSamples) {
                     final int amount = samplesRead == 0 ? numChannels :
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java b/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
index 887b868..d836079 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
@@ -29,7 +29,6 @@
 import com.android.compatibility.common.util.CtsAndroidTestCase;
 
 import javax.annotation.concurrent.GuardedBy;
-import java.io.IOException;
 import java.io.InputStream;
 import java.util.concurrent.Executor;
 
@@ -75,54 +74,69 @@
 
     public void testMP3AudioTrackOffload() throws Exception {
         testAudioTrackOffload(R.raw.sine1khzs40dblong,
-                              /* bitRateInkbps= */ 192,
-                              getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
+                /* bitRateInkbps= */ 192,
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
     }
 
     public void testOpusAudioTrackOffload() throws Exception {
         testAudioTrackOffload(R.raw.testopus,
-                              /* bitRateInkbps= */ 118, // Average
-                              getAudioFormatWithEncoding(AudioFormat.ENCODING_OPUS));
+                /* bitRateInkbps= */ 118, // Average
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_OPUS));
     }
 
-    /** Test offload of an audio resource that MUST be at least 3sec long. */
+    private AudioTrack getOffloadAudioTrack(@RawRes int audioRes, int bitRateInkbps,
+                                            AudioFormat audioFormat) {
+        if (!AudioManager.isOffloadedPlaybackSupported(audioFormat, DEFAULT_ATTR)) {
+            Log.i(TAG, "skipping testAudioTrackOffload as offload encoding "
+                    + audioFormat.getEncoding() + " is not supported");
+            // cannot test if offloading is not supported
+            return null;
+        }
+
+        int bufferSizeInBytes = bitRateInkbps * 1000 * BUFFER_SIZE_SEC / 8;
+        // format is offloadable, test playback head is progressing
+        AudioTrack track = new AudioTrack.Builder()
+                .setAudioAttributes(DEFAULT_ATTR)
+                .setAudioFormat(audioFormat)
+                .setTransferMode(AudioTrack.MODE_STREAM)
+                .setBufferSizeInBytes(bufferSizeInBytes)
+                .setOffloadedPlayback(true)
+                .build();
+        assertNotNull("Couldn't create offloaded AudioTrack", track);
+        assertEquals("Unexpected track sample rate", AUDIOTRACK_DEFAULT_SAMPLE_RATE,
+                track.getSampleRate());
+        assertEquals("Unexpected track channel mask", AUDIOTRACK_DEFAULT_CHANNEL_MASK,
+                track.getChannelConfiguration());
+        return track;
+    }
+
+    /**
+     * Test offload of an audio resource that MUST be at least 3sec long.
+     */
     private void testAudioTrackOffload(@RawRes int audioRes, int bitRateInkbps,
                                        AudioFormat audioFormat) throws Exception {
         AudioTrack track = null;
-        int bufferSizeInBytes3sec = bitRateInkbps * 1024 * BUFFER_SIZE_SEC / 8;
         try (AssetFileDescriptor audioToOffload = getContext().getResources()
                 .openRawResourceFd(audioRes);
              InputStream audioInputStream = audioToOffload.createInputStream()) {
 
-            if (!AudioManager.isOffloadedPlaybackSupported(audioFormat, DEFAULT_ATTR)) {
-                Log.i(TAG, "skipping testAudioTrackOffload as offload for encoding "
-                           + audioFormat.getEncoding() + " is not supported");
-                // cannot test if offloading is not supported
+            track = getOffloadAudioTrack(audioRes, bitRateInkbps, audioFormat);
+            if (track == null) {
                 return;
             }
 
-            // format is offloadable, test playback head is progressing
-            track = new AudioTrack.Builder()
-                    .setAudioAttributes(DEFAULT_ATTR)
-                    .setAudioFormat(audioFormat)
-                    .setTransferMode(AudioTrack.MODE_STREAM)
-                    .setBufferSizeInBytes(bufferSizeInBytes3sec)
-                    .setOffloadedPlayback(true).build();
-            assertNotNull("Couldn't create offloaded AudioTrack", track);
-            assertEquals("Unexpected track sample rate", 44100, track.getSampleRate());
-            assertEquals("Unexpected track channel config", AudioFormat.CHANNEL_OUT_STEREO,
-                    track.getChannelConfiguration());
-
             try {
                 track.registerStreamEventCallback(mExec, null);
                 fail("Shouldn't be able to register null StreamEventCallback");
-            } catch (Exception e) { }
+            } catch (Exception e) {
+            }
             track.registerStreamEventCallback(mExec, mCallback);
 
+            int bufferSizeInBytes3sec = bitRateInkbps * 1000 * BUFFER_SIZE_SEC / 8;
             final byte[] data = new byte[bufferSizeInBytes3sec];
             final int read = audioInputStream.read(data);
             assertEquals("Could not read enough audio from the resource file",
-                         bufferSizeInBytes3sec, read);
+                    bufferSizeInBytes3sec, read);
 
             track.play();
             int written = 0;
@@ -180,6 +194,70 @@
         return (SystemClock.uptimeMillis() - checkStart);
     }
 
+    private AudioTrack allocNonOffloadAudioTrack() {
+        // Attrributes the AudioTrack are irrelevant in this case. We just need to provide
+        // an AudioTrack that IS NOT offloaded so that we can demonstrate failure.
+        AudioTrack track = new AudioTrack.Builder()
+                .setBufferSizeInBytes(2048/*arbitrary*/)
+                .build();
+
+        assert(track != null);
+        return track;
+    }
+
+     // Arbitrary values..
+    private static final int TEST_DELAY = 50;
+    private static final int TEST_PADDING = 100;
+    public void testOffloadPadding() {
+        AudioTrack track =
+                getOffloadAudioTrack(R.raw.sine1khzs40dblong,
+                /* bitRateInkbps= */ 192,
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
+        if (track == null) {
+            return;
+        }
+
+        assertTrue(track.getOffloadPadding() >= 0);
+
+        track.setOffloadDelayPadding(0 /*delayInFrames*/, 0 /*paddingInFrames*/);
+
+        int offloadDelay;
+        offloadDelay = track.getOffloadDelay();
+        assertEquals(0, offloadDelay);
+
+        int padding = track.getOffloadPadding();
+        assertEquals(0, padding);
+
+        track.setOffloadDelayPadding(
+                TEST_DELAY /*delayInFrames*/,
+                TEST_PADDING /*paddingInFrames*/);
+        offloadDelay = track.getOffloadDelay();
+        assertEquals(TEST_DELAY, offloadDelay);
+        padding = track.getOffloadPadding();
+        assertEquals(TEST_PADDING, padding);
+    }
+
+    public void testIsOffloadedPlayback() {
+        // non-offloaded case
+        AudioTrack nonOffloadTrack = allocNonOffloadAudioTrack();
+        assertFalse(nonOffloadTrack.isOffloadedPlayback());
+
+        // offloaded case
+        AudioTrack offloadTrack =
+                getOffloadAudioTrack(R.raw.sine1khzs40dblong,
+                        /* bitRateInkbps= */ 192,
+                        getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
+        assertTrue(offloadTrack.isOffloadedPlayback());
+    }
+
+    public void testSetOffloadEndOfStreamWithNonOffloadedTrack() {
+        // Non-offload case
+        AudioTrack nonOffloadTrack = allocNonOffloadAudioTrack();
+        assertFalse(nonOffloadTrack.isOffloadedPlayback());
+        org.testng.Assert.assertThrows(IllegalStateException.class,
+                () -> nonOffloadTrack.setOffloadEndOfStream());
+    }
+
     private static AudioFormat getAudioFormatWithEncoding(int encoding) {
        return new AudioFormat.Builder()
             .setEncoding(encoding)
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index e0a8d8b..2b2138d 100755
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -2145,6 +2145,7 @@
                 streamName);
     }
 
+    // Note: this test may fail if playing through a remote device such as Bluetooth.
     private void doTestTimestamp(int sampleRate, int channelMask, int encoding, int transferMode,
             String streamName) throws Exception {
         // constants for test
@@ -2153,6 +2154,7 @@
         final int TEST_USAGE = AudioAttributes.USAGE_MEDIA;
 
         final int MILLIS_PER_SECOND = 1000;
+        final int FRAME_TOLERANCE = sampleRate * TEST_BUFFER_MS / MILLIS_PER_SECOND;
 
         // -------- initialization --------------
         final int frameSize =
@@ -2191,62 +2193,79 @@
             track.play();
 
             // Android nanoTime implements MONOTONIC, same as our audio timestamps.
-            final long trackStartTimeNs = System.nanoTime();
 
             final ByteBuffer data = ByteBuffer.allocate(frameCount * frameSize);
             data.order(java.nio.ByteOrder.nativeOrder()).limit(frameCount * frameSize);
             final AudioTimestamp timestamp = new AudioTimestamp();
 
             long framesWritten = 0;
-            final AudioHelper.TimestampVerifier tsVerifier =
-                    new AudioHelper.TimestampVerifier(TAG, sampleRate, isProAudioDevice());
-            for (int i = 0; i < TEST_LOOP_CNT; ++i) {
-                final long trackWriteTimeNs = System.nanoTime();
 
-                data.position(0);
-                assertEquals("write did not complete",
-                        data.limit(), track.write(data, data.limit(), AudioTrack.WRITE_BLOCKING));
-                assertEquals("write did not fill buffer",
-                        data.position(), data.limit());
-                framesWritten += data.limit() / frameSize;
+            // We start data delivery twice, the second start simulates restarting
+            // the track after a fully drained underrun (important case for Android TV).
+            for (int start = 0; start < 2; ++start) {
+                final long trackStartTimeNs = System.nanoTime();
+                final AudioHelper.TimestampVerifier tsVerifier =
+                        new AudioHelper.TimestampVerifier(
+                                TAG + "(start " + start + ")",
+                                sampleRate, framesWritten, isProAudioDevice());
+                for (int i = 0; i < TEST_LOOP_CNT; ++i) {
+                    final long trackWriteTimeNs = System.nanoTime();
 
-                // track.getTimestamp may return false if there are no physical HAL outputs.
-                // This may occur on TV devices without connecting an HDMI monitor.
-                // It may also be true immediately after start-up, as the mixing thread could
-                // be idle, but since we've already pushed much more than the minimum buffer size,
-                // that is unlikely.
-                // Nevertheless, we don't want to have unnecessary failures, so we ignore the
-                // first iteration if we don't get a timestamp.
-                final boolean result = track.getTimestamp(timestamp);
-                assertTrue("timestamp could not be read", result || i == 0);
-                if (!result) {
-                    continue;
+                    data.position(0);
+                    assertEquals("write did not complete",
+                            data.limit(), track.write(data, data.limit(),
+                            AudioTrack.WRITE_BLOCKING));
+                    assertEquals("write did not fill buffer",
+                            data.position(), data.limit());
+                    framesWritten += data.limit() / frameSize;
+
+                    // track.getTimestamp may return false if there are no physical HAL outputs.
+                    // This may occur on TV devices without connecting an HDMI monitor.
+                    // It may also be true immediately after start-up, as the mixing thread could
+                    // be idle, but since we've already pushed much more than the
+                    // minimum buffer size, that is unlikely.
+                    // Nevertheless, we don't want to have unnecessary failures, so we ignore the
+                    // first iteration if we don't get a timestamp.
+                    final boolean result = track.getTimestamp(timestamp);
+                    assertTrue("timestamp could not be read", result || i == 0);
+                    if (!result) {
+                        continue;
+                    }
+
+                    tsVerifier.add(timestamp);
+
+                    // Ensure that seen is greater than presented.
+                    // This is an "on-the-fly" read without pausing because pausing may cause the
+                    // timestamp to become stale and affect our jitter measurements.
+                    final long framesPresented = timestamp.framePosition;
+                    final int framesSeen = track.getPlaybackHeadPosition();
+                    assertTrue("server frames ahead of client frames",
+                            framesWritten >= framesSeen);
+                    assertTrue("presented frames ahead of server frames",
+                            framesSeen >= framesPresented);
                 }
+                // Full drain.
+                Thread.sleep(1000 /* millis */);
+                // check that we are really at the end of playback.
+                assertTrue("timestamp should be valid while draining",
+                        track.getTimestamp(timestamp));
+                // Fast tracks and sw emulated tracks may not fully drain.
+                // We log the status here.
+                if (framesWritten != timestamp.framePosition) {
+                    Log.d(TAG, "timestamp should fully drain.  written: "
+                            + framesWritten + " position: " + timestamp.framePosition);
+                }
+                final long framesLowerLimit = framesWritten - FRAME_TOLERANCE;
+                assertTrue("timestamp frame position needs to be close to written: "
+                                + timestamp.framePosition  + " >= " + framesLowerLimit,
+                        timestamp.framePosition >= framesLowerLimit);
 
-                tsVerifier.add(timestamp);
+                assertTrue("timestamp should not advance during underrun: "
+                        + timestamp.framePosition  + " <= " + framesWritten,
+                        timestamp.framePosition <= framesWritten);
 
-                // Ensure that seen is greater than presented.
-                // This is an "on-the-fly" read without pausing because pausing may cause the
-                // timestamp to become stale and affect our jitter measurements.
-                final long framesPresented = timestamp.framePosition;
-                final int framesSeen = track.getPlaybackHeadPosition();
-                assertTrue("server frames ahead of client frames",
-                        framesWritten >= framesSeen);
-                assertTrue("presented frames ahead of server frames",
-                        framesSeen >= framesPresented);
+                tsVerifier.verifyAndLog(trackStartTimeNs, streamName);
             }
-            // Full drain.
-            Thread.sleep(1000 /* millis */);
-            // check that we are really at the end of playback.
-            assertTrue("timestamp should be valid while draining", track.getTimestamp(timestamp));
-            // fast tracks and sw emulated tracks may not fully drain.  we log the status here.
-            if (framesWritten != timestamp.framePosition) {
-                Log.d(TAG, "timestamp should fully drain.  written: "
-                        + framesWritten + " position: " + timestamp.framePosition);
-            }
-
-            tsVerifier.verifyAndLog(trackStartTimeNs, streamName);
-
         } finally {
             track.release();
         }
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index 3c35b88..60a6af1 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -323,6 +323,7 @@
                                 sampleRate,
                                 channelCount);
                         codec.configure(desiredFormat, null, null, 0);
+                        codec.start();
 
                         Log.d(TAG, "codec: " + codecInfo.getName() +
                                 " sample rate: " + sampleRate +
@@ -1773,6 +1774,32 @@
         }
     }
 
+    protected static int getOutputFormatInteger(MediaCodec codec, String key) {
+        if (codec == null) {
+            fail("Null MediaCodec before attempting to retrieve output format key " + key);
+        }
+        MediaFormat format = null;
+        try {
+            format = codec.getOutputFormat();
+        } catch (Exception e) {
+            fail("Exception " + e + " when attempting to obtain output format");
+        }
+        if (format == null) {
+            fail("Null output format returned from MediaCodec");
+        }
+        try {
+            return format.getInteger(key);
+        } catch (NullPointerException e) {
+            fail("Key " + key + " not present in output format");
+        } catch (ClassCastException e) {
+            fail("Key " + key + " not stored as integer in output format");
+        } catch (Exception e) {
+            fail("Exception " + e + " when attempting to retrieve output format key " + key);
+        }
+        // never used
+        return Integer.MIN_VALUE;
+    }
+
     // Class handling all audio parameters relevant for testing
     protected static class AudioParameter {
 
diff --git a/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java b/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java
index 3af1400..e33505a 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java
@@ -586,8 +586,8 @@
             if(!runtimeChange) {
                 // check if MediaCodec gives back correct drc parameters
                 if (drcParams.mDecoderTargetLevel != 0) {
-                    final int targetLevelFromCodec = codec.getOutputFormat()
-                            .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                    final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
                     if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
                         fail("DRC Target Ref Level received from MediaCodec is not the level set");
                     }
@@ -709,19 +709,18 @@
         // check if MediaCodec gives back correct drc parameters (R and above)
         if (drcParams != null && sIsAndroidRAndAbove) {
             if (drcParams.mDecoderTargetLevel != 0) {
-                final int targetLevelFromCodec = codec.getOutputFormat()
-                        .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
                 if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
                     fail("DRC Target Ref Level received from MediaCodec is not the level set");
                 }
             }
 
-            final MediaFormat outputFormat = codec.getOutputFormat();
-            final int cutFromCodec = outputFormat.getInteger(
+            final int cutFromCodec = DecoderTest.getOutputFormatInteger(codec,
                     MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR);
             assertEquals("Attenuation factor received from MediaCodec differs from set:",
                     drcParams.mCut, cutFromCodec);
-            final int boostFromCodec = outputFormat.getInteger(
+            final int boostFromCodec = DecoderTest.getOutputFormatInteger(codec,
                     MediaFormat.KEY_AAC_DRC_BOOST_FACTOR);
             assertEquals("Boost factor received from MediaCodec differs from set:",
                     drcParams.mBoost, boostFromCodec);
@@ -729,8 +728,8 @@
 
         // expectedOutputLoudness == -2 indicates that output loudness is not tested
         if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) {
-            final int outputLoudnessFromCodec = codec.getOutputFormat()
-                    .getInteger(MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
+            final int outputLoudnessFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                    MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
             if (outputLoudnessFromCodec != expectedOutputLoudness) {
                 fail("Received decoder output loudness is not the expected value");
             }
diff --git a/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java b/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java
index 13b7928..83bb53f 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java
@@ -1287,22 +1287,22 @@
         if (drcParams != null && sIsAndroidRAndAbove) { // querying output format requires R
             if(!runtimeChange) {
                 if (drcParams.mAlbumMode != 0) {
-                    int albumModeFromCodec = codec.getOutputFormat()
-                            .getInteger(MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
+                    int albumModeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
                     if (albumModeFromCodec != drcParams.mAlbumMode) {
                         fail("Drc AlbumMode received from MediaCodec is not the Album Mode set");
                     }
                 }
                 if (drcParams.mEffectType != 0) {
-                    final int effectTypeFromCodec = codec.getOutputFormat()
-                            .getInteger(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
+                    final int effectTypeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
                     if (effectTypeFromCodec != drcParams.mEffectType) {
                         fail("Drc Effect Type received from MediaCodec is not the Effect Type set");
                     }
                 }
                 if (drcParams.mDecoderTargetLevel != 0) {
-                    final int targetLevelFromCodec = codec.getOutputFormat()
-                            .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                    final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
                     if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
                         fail("Drc Target Reference Level received from MediaCodec is not the Target Reference Level set");
                     }
@@ -1443,31 +1443,30 @@
         // check if MediaCodec gives back correct drc parameters
         if (drcParams != null && sIsAndroidRAndAbove) {
             if (drcParams.mAlbumMode != 0) {
-                final int albumModeFromCodec = codec.getOutputFormat()
-                        .getInteger(MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
+                final int albumModeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
                 assertEquals("DRC AlbumMode received from MediaCodec is not the Album Mode set"
                         + " runtime:" + runtimeChange, drcParams.mAlbumMode, albumModeFromCodec);
             }
             if (drcParams.mEffectType != 0) {
-                final int effectTypeFromCodec = codec.getOutputFormat()
-                        .getInteger(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
+                final int effectTypeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
                 assertEquals("DRC Effect Type received from MediaCodec is not the Effect Type set"
                         + " runtime:" + runtimeChange, drcParams.mEffectType, effectTypeFromCodec);
             }
             if (drcParams.mDecoderTargetLevel != 0) {
-                final int targetLevelFromCodec = codec.getOutputFormat()
-                        .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
                 assertEquals("DRC Target Ref Level received from MediaCodec is not the level set"
                         + " runtime:" + runtimeChange,
                         drcParams.mDecoderTargetLevel, targetLevelFromCodec);
             }
 
-            final MediaFormat outputFormat = codec.getOutputFormat();
-            final int cutFromCodec = outputFormat.getInteger(
+            final int cutFromCodec = DecoderTest.getOutputFormatInteger(codec,
                     MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR);
             assertEquals("Attenuation factor received from MediaCodec differs from set:",
                     drcParams.mCut, cutFromCodec);
-            final int boostFromCodec = outputFormat.getInteger(
+            final int boostFromCodec = DecoderTest.getOutputFormatInteger(codec,
                     MediaFormat.KEY_AAC_DRC_BOOST_FACTOR);
             assertEquals("Boost factor received from MediaCodec differs from set:",
                     drcParams.mBoost, boostFromCodec);
@@ -1475,8 +1474,8 @@
 
         // expectedOutputLoudness == -2 indicates that output loudness is not tested
         if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) {
-            final int outputLoudnessFromCodec = codec.getOutputFormat()
-                    .getInteger(MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
+            final int outputLoudnessFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                    MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
             if (outputLoudnessFromCodec != expectedOutputLoudness) {
                 fail("Received decoder output loudness is not the expected value");
             }
diff --git a/tests/tests/media/src/android/media/cts/DrmInitDataTest.java b/tests/tests/media/src/android/media/cts/DrmInitDataTest.java
new file mode 100644
index 0000000..ee2fa86
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/DrmInitDataTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.media.DrmInitData;
+import android.test.AndroidTestCase;
+
+import java.util.UUID;
+
+public class DrmInitDataTest extends AndroidTestCase {
+
+    public void testSchemeInitDataConstructor() {
+        UUID uuid = new UUID(1, 1);
+        String mimeType = "mime/type";
+        byte[] data = new byte[0];
+        DrmInitData.SchemeInitData schemeInitData =
+                new DrmInitData.SchemeInitData(uuid, mimeType, data);
+        assertSame(uuid, schemeInitData.uuid);
+        assertSame(mimeType, schemeInitData.mimeType);
+        assertSame(data, schemeInitData.data);
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
index 4545edf..039bc22 100644
--- a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
+++ b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
@@ -79,6 +79,7 @@
             "webp_lossless_without_exif.webp";
     private static final String PNG_WITH_EXIF_BYTE_ORDER_II = "png_with_exif_byte_order_ii.png";
     private static final String PNG_WITHOUT_EXIF = "png_without_exif.png";
+    private static final String JPEG_WITH_DATETIME_TAG = "jpeg_with_datetime_tag.jpg";
 
     private static final String[] EXIF_TAGS = {
             ExifInterface.TAG_MAKE,
@@ -743,16 +744,21 @@
         writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING);
     }
 
-    public void testSetDateTime() throws IOException {
+    public void testGetSetDateTime() throws IOException {
+        final long expectedDatetimeValue = 1454059947000L;
         final String dateTimeValue = "2017:02:02 22:22:22";
         final String dateTimeOriginalValue = "2017:01:01 11:11:11";
 
-        File srcFile = new File(Environment.getExternalStorageDirectory(),
-                EXTERNAL_BASE_DIRECTORY + JPEG_WITH_EXIF_BYTE_ORDER_II);
-        File imageFile = clone(srcFile);
+        File imageFile = new File(Environment.getExternalStorageDirectory(),
+                EXTERNAL_BASE_DIRECTORY + JPEG_WITH_DATETIME_TAG);
+        stageFile(R.raw.jpeg_with_datetime_tag, imageFile);
 
-        FileUtils.copyFileOrThrow(srcFile, imageFile);
         ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+        assertEquals(expectedDatetimeValue, exif.getDateTime());
+        assertEquals(expectedDatetimeValue, exif.getDateTimeOriginal());
+        assertEquals(expectedDatetimeValue, exif.getDateTimeDigitized());
+        assertEquals(expectedDatetimeValue, exif.getGpsDateTime());
+
         exif.setAttribute(ExifInterface.TAG_DATETIME, dateTimeValue);
         exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTimeOriginalValue);
         exif.saveAttributes();
diff --git a/tests/tests/media/src/android/media/cts/HapticGeneratorTest.java b/tests/tests/media/src/android/media/cts/HapticGeneratorTest.java
new file mode 100644
index 0000000..2dc8b8b
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/HapticGeneratorTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.media.AudioManager;
+import android.media.audiofx.HapticGenerator;
+
+@NonMediaMainlineTest
+public class HapticGeneratorTest extends PostProcTestBase {
+
+    private String TAG = "HapticGeneratorTest";
+
+    //-----------------------------------------------------------------
+    // HAPTIC GENERATOR TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    public void test0_0ConstructorAndRelease() throws Exception {
+        if (!HapticGenerator.isAvailable()) {
+            // HapticGenerator will only be created on devices supporting haptic playback
+            return;
+        }
+        HapticGenerator effect = createHapticGenerator();
+        // If the effect is null, it must fail creation.
+        effect.release();
+    }
+
+    //-----------------------------------------------------------------
+    // 1 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 1.0: test setEnabled() and getEnabled() in valid state
+    public void test1_0SetEnabledGetEnabled() throws Exception {
+        if (!HapticGenerator.isAvailable()) {
+            // HapticGenerator will only be created on devices supporting haptic playback
+            return;
+        }
+        HapticGenerator effect = createHapticGenerator();
+        try {
+            effect.setEnabled(true);
+            assertTrue("invalid state from getEnabled", effect.getEnabled());
+            effect.setEnabled(false);
+            assertFalse("invalid state from getEnabled", effect.getEnabled());
+            // test passed
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            effect.release();
+        }
+    }
+
+    private HapticGenerator createHapticGenerator() {
+        try {
+            HapticGenerator effect = HapticGenerator.create(getSessionId());
+            try {
+                assertTrue("invalid effect ID", (effect.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("HapticGenerator not initialized");
+            }
+            return effect;
+        } catch (IllegalArgumentException e) {
+            fail("HapticGenerator not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } catch (RuntimeException e) {
+            fail("Unexpected run time error: " + e);
+        }
+        return null;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaActivityTest.java b/tests/tests/media/src/android/media/cts/MediaActivityTest.java
index 6e6658b..8cbe255 100644
--- a/tests/tests/media/src/android/media/cts/MediaActivityTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaActivityTest.java
@@ -33,6 +33,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.util.Log;
 import android.view.KeyEvent;
 
 import androidx.test.InstrumentationRegistry;
@@ -55,7 +56,7 @@
 import java.util.concurrent.TimeUnit;
 
 /**
- * Test media activity which has called {@link Activity#setMediaController}.
+ * Test {@link MediaSessionTestActivity} which has called {@link Activity#setMediaController}.
  */
 @NonMediaMainlineTest
 @LargeTest
@@ -140,7 +141,13 @@
 
         for (int stream : mStreamVolumeMap.keySet()) {
             int volume = mStreamVolumeMap.get(stream);
-            mAudioManager.setStreamVolume(stream, volume, 0);
+            try {
+                mAudioManager.setStreamVolume(stream, volume, /* flag= */ 0);
+            } catch (SecurityException e) {
+                Log.w(TAG, "Failed to restore volume. The test probably had changed DnD mode"
+                        + ", stream=" + stream + ", originalVolume="
+                        + volume + ", currentVolume=" + mAudioManager.getStreamVolume(stream));
+            }
         }
     }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowserServiceTest.java b/tests/tests/media/src/android/media/cts/MediaBrowserServiceTest.java
index e7ef364..43cf024 100644
--- a/tests/tests/media/src/android/media/cts/MediaBrowserServiceTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaBrowserServiceTest.java
@@ -15,9 +15,18 @@
  */
 package android.media.cts;
 
+import static android.media.browse.MediaBrowser.MediaItem.FLAG_PLAYABLE;
+import static android.media.cts.MediaBrowserServiceTestService.KEY_PARENT_MEDIA_ID;
+import static android.media.cts.MediaBrowserServiceTestService.KEY_SERVICE_COMPONENT_NAME;
+import static android.media.cts.MediaBrowserServiceTestService.TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED;
+import static android.media.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS;
+import static android.media.cts.MediaSessionTestService.STEP_CHECK;
+import static android.media.cts.MediaSessionTestService.STEP_CLEAN_UP;
+import static android.media.cts.MediaSessionTestService.STEP_SET_UP;
 import static android.media.cts.Utils.compareRemoteUserInfo;
 
 import android.content.ComponentName;
+import android.media.MediaDescription;
 import android.media.browse.MediaBrowser;
 import android.media.browse.MediaBrowser.MediaItem;
 import android.media.session.MediaSessionManager.RemoteUserInfo;
@@ -27,6 +36,9 @@
 import android.service.media.MediaBrowserService.BrowserRoot;
 import android.test.InstrumentationTestCase;
 
+import androidx.test.core.app.ApplicationProvider;
+
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -102,7 +114,7 @@
     private Bundle mRootHints;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
@@ -125,6 +137,14 @@
         assertNotNull(mMediaBrowserService);
     }
 
+    @Override
+    public void tearDown() {
+        if (mMediaBrowser != null) {
+            mMediaBrowser.disconnect();
+            mMediaBrowser = null;
+        }
+    }
+
     public void testGetSessionToken() {
         assertEquals(StubMediaBrowserService.sSession.getSessionToken(),
                 mMediaBrowserService.getSessionToken());
@@ -232,6 +252,41 @@
         assertEquals(val, browserRoot.getExtras().getString(key));
     }
 
+    /**
+     * Check that a series of {@link MediaBrowserService#notifyChildrenChanged} does not break
+     * {@link MediaBrowser} on the remote process due to binder buffer overflow.
+     */
+    public void testSeriesOfNotifyChildrenChanged() throws Exception {
+        String parentMediaId = "testSeriesOfNotifyChildrenChanged";
+        int numberOfCalls = 100;
+        int childrenSize = 1_000;
+        List<MediaItem> children = new ArrayList<>();
+        for (int id = 0; id < childrenSize; id++) {
+            MediaDescription description = new MediaDescription.Builder()
+                    .setMediaId(Integer.toString(id)).build();
+            children.add(new MediaItem(description, FLAG_PLAYABLE));
+        }
+        mMediaBrowserService.putChildrenToMap(parentMediaId, children);
+
+        try (RemoteService.Invoker invoker = new RemoteService.Invoker(
+                ApplicationProvider.getApplicationContext(),
+                MediaBrowserServiceTestService.class,
+                TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED)) {
+            Bundle args = new Bundle();
+            args.putParcelable(KEY_SERVICE_COMPONENT_NAME, TEST_BROWSER_SERVICE);
+            args.putString(KEY_PARENT_MEDIA_ID, parentMediaId);
+            args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * childrenSize);
+            invoker.run(STEP_SET_UP, args);
+            for (int i = 0; i < numberOfCalls; i++) {
+                mMediaBrowserService.notifyChildrenChanged(parentMediaId);
+            }
+            invoker.run(STEP_CHECK);
+            invoker.run(STEP_CLEAN_UP);
+        }
+
+        mMediaBrowserService.removeChildrenFromMap(parentMediaId);
+    }
+
     private void assertRootHints(MediaItem item) {
         Bundle rootHints = item.getDescription().getExtras();
         assertNotNull(rootHints);
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowserServiceTestService.java b/tests/tests/media/src/android/media/cts/MediaBrowserServiceTestService.java
new file mode 100644
index 0000000..0db43d9
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaBrowserServiceTestService.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MediaBrowserServiceTestService extends RemoteService {
+    public static final int TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED = 0;
+
+    public static final int STEP_SET_UP = 0;
+    public static final int STEP_CHECK = 1;
+    public static final int STEP_CLEAN_UP = 2;
+
+    public static final String KEY_SERVICE_COMPONENT_NAME = "serviceComponentName";
+    public static final String KEY_PARENT_MEDIA_ID = "parentMediaId";
+    public static final String KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS = "expectedTotalNumberOfItems";
+
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+    private MediaBrowser mMediaBrowser;
+    private CountDownLatch mAllItemsNotified;
+
+    private void testSeriesOfNotifyChildrenChanged_setUp(Bundle args) throws Exception {
+        ComponentName componentName = args.getParcelable(KEY_SERVICE_COMPONENT_NAME);
+        String parentMediaId = args.getString(KEY_PARENT_MEDIA_ID);
+        int expectedTotalNumberOfItems = args.getInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS);
+
+        mAllItemsNotified = new CountDownLatch(1);
+        AtomicInteger numberOfItems = new AtomicInteger();
+        CountDownLatch subscribed = new CountDownLatch(1);
+        MediaBrowser.ConnectionCallback connectionCallback = new MediaBrowser.ConnectionCallback();
+        MediaBrowser.SubscriptionCallback subscriptionCallback =
+                new MediaBrowser.SubscriptionCallback() {
+                    @Override
+                    public void onChildrenLoaded(String parentId, List<MediaItem> children) {
+                        if (parentMediaId.equals(parentId) && children != null) {
+                            if (subscribed.getCount() > 0) {
+                                subscribed.countDown();
+                                return;
+                            }
+                            if (numberOfItems.addAndGet(children.size())
+                                    >= expectedTotalNumberOfItems) {
+                                mAllItemsNotified.countDown();
+                            }
+                        }
+                    }
+                };
+        mMainHandler.post(() -> {
+            mMediaBrowser = new MediaBrowser(this, componentName, connectionCallback, null);
+            mMediaBrowser.connect();
+            mMediaBrowser.subscribe(parentMediaId, subscriptionCallback);
+        });
+        assertTrue(subscribed.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSeriesOfNotifyChildrenChanged_check() throws Exception {
+        assertTrue(mAllItemsNotified.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSeriesOfNotifyChildrenChanged_cleanUp() {
+        mMainHandler.post(() -> {
+            mMediaBrowser.disconnect();
+            mMediaBrowser = null;
+        });
+        mAllItemsNotified = null;
+    }
+
+    @Override
+    public void onRun(int testId, int step, @Nullable Bundle args) throws Exception {
+        if (testId == TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED) {
+            if (step == STEP_SET_UP) {
+                testSeriesOfNotifyChildrenChanged_setUp(args);
+            } else if (step == STEP_CHECK) {
+                testSeriesOfNotifyChildrenChanged_check();
+            } else if (step == STEP_CLEAN_UP) {
+                testSeriesOfNotifyChildrenChanged_cleanUp();
+            } else {
+                throw new IllegalArgumentException("Unknown step=" + step);
+            }
+        } else {
+            throw new IllegalArgumentException("Unknown testId=" + testId);
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowserTest.java b/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
index 8c7d63d..9be25c3 100644
--- a/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
@@ -54,6 +54,14 @@
 
     private MediaBrowser mMediaBrowser;
 
+    @Override
+    public void tearDown() {
+        if (mMediaBrowser != null) {
+            mMediaBrowser.disconnect();
+            mMediaBrowser = null;
+        }
+    }
+
     public void testMediaBrowser() {
         resetCallbacks();
         createMediaBrowser(TEST_BROWSER_SERVICE);
@@ -566,7 +574,7 @@
             mLastErrorId = id;
             mLastOptions = options;
         }
-}
+    }
 
     private static class StubItemCallback extends MediaBrowser.ItemCallback {
         private volatile MediaBrowser.MediaItem mLastMediaItem;
diff --git a/tests/tests/media/src/android/media/cts/MediaButtonBroadcastReceiver.java b/tests/tests/media/src/android/media/cts/MediaButtonBroadcastReceiver.java
new file mode 100644
index 0000000..1d320d8
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaButtonBroadcastReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.view.KeyEvent;
+
+import java.util.function.Consumer;
+
+public class MediaButtonBroadcastReceiver extends BroadcastReceiver {
+    public static Consumer<KeyEvent> mCallback;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        assertEquals(Intent.ACTION_MEDIA_BUTTON, intent.getAction());
+        KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+        synchronized (MediaButtonBroadcastReceiver.class) {
+            if (mCallback != null) {
+                mCallback.accept(keyEvent);
+            }
+        }
+    }
+
+    public synchronized static void setCallback(Consumer<KeyEvent> callback) {
+        synchronized (MediaButtonBroadcastReceiver.class) {
+            mCallback = callback;
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaButtonReceiver.java b/tests/tests/media/src/android/media/cts/MediaButtonReceiver.java
deleted file mode 100644
index 30e8150..0000000
--- a/tests/tests/media/src/android/media/cts/MediaButtonReceiver.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.view.KeyEvent;
-
-import java.util.function.Consumer;
-
-public class MediaButtonReceiver extends BroadcastReceiver {
-    public static Consumer<KeyEvent> mCallback;
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        assertEquals(Intent.ACTION_MEDIA_BUTTON, intent.getAction());
-        KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-        synchronized (MediaButtonReceiver.class) {
-            if (mCallback != null) {
-                mCallback.accept(keyEvent);
-            }
-        }
-    }
-
-    public synchronized static void setCallback(Consumer<KeyEvent> callback) {
-        synchronized (MediaButtonReceiver.class) {
-            mCallback = callback;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaButtonReceiverService.java b/tests/tests/media/src/android/media/cts/MediaButtonReceiverService.java
new file mode 100644
index 0000000..def8cee
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaButtonReceiverService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.view.KeyEvent;
+
+import java.util.function.Consumer;
+
+public class MediaButtonReceiverService extends IntentService {
+    private static final String TAG = "MediaButtonReceiverService";
+    public static Consumer<KeyEvent> mCallback;
+
+    public MediaButtonReceiverService() {
+        super(TAG);
+    }
+
+    public synchronized static void setCallback(Consumer<KeyEvent> callback) {
+        synchronized (MediaButtonReceiverService.class) {
+            mCallback = callback;
+        }
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        assertEquals(Intent.ACTION_MEDIA_BUTTON, intent.getAction());
+        KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+        synchronized (MediaButtonReceiverService.class) {
+            if (mCallback != null) {
+                mCallback.accept(keyEvent);
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaControllerTest.java b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
index b71b04a..f65f9ab 100644
--- a/tests/tests/media/src/android/media/cts/MediaControllerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
@@ -68,6 +68,15 @@
                 getContext().getPackageName(), Process.myPid(), Process.myUid());
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (mSession != null) {
+            mSession.release();
+            mSession = null;
+        }
+    }
+
     public void testGetPackageName() {
         assertEquals(getContext().getPackageName(), mController.getPackageName());
     }
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataTest.java
new file mode 100644
index 0000000..465a98f
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.graphics.Bitmap;
+import android.media.MediaMetadata;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link MediaMetadata}.
+ */
+// TODO(b/168668505): Add tests for other methods.
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@NonMediaMainlineTest
+public class MediaMetadataTest {
+
+    @Test
+    public void getBitmapDimensionLimit_returnsZeroWhenNotSet() {
+        MediaMetadata metadata = new MediaMetadata.Builder().build();
+        assertEquals(0, metadata.getBitmapDimensionLimit());
+    }
+
+    @Test
+    public void builder_setBitmapDimensionLimit_bitmapsAreScaledDown() {
+        // A large bitmap (64MB).
+        final int originalWidth = 4096;
+        final int originalHeight = 4096;
+        Bitmap testBitmap = Bitmap.createBitmap(
+                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
+
+        final int testBitmapDimensionLimit = 16;
+
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, testBitmap)
+                .setBitmapDimensionLimit(testBitmapDimensionLimit)
+                .build();
+        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
+
+        Bitmap scaledDownBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        assertNotNull(scaledDownBitmap);
+        assertTrue(scaledDownBitmap.getWidth() <= testBitmapDimensionLimit);
+        assertTrue(scaledDownBitmap.getHeight() <= testBitmapDimensionLimit);
+    }
+
+    @Test
+    public void builder_setBitmapDimensionLimit_bitmapsAreNotScaledDown() {
+        // A small bitmap.
+        final int originalWidth = 16;
+        final int originalHeight = 16;
+        Bitmap testBitmap = Bitmap.createBitmap(
+                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
+
+        // The limit is larger than the width/height.
+        final int testBitmapDimensionLimit = 256;
+
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, testBitmap)
+                .setBitmapDimensionLimit(testBitmapDimensionLimit)
+                .build();
+        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
+
+        Bitmap notScaledDownBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        assertNotNull(notScaledDownBitmap);
+        assertEquals(originalWidth, notScaledDownBitmap.getWidth());
+        assertEquals(originalHeight, notScaledDownBitmap.getHeight());
+    }
+
+    @Test
+    public void builder_setMaxBitmapDimensionLimit_unsetLimit() {
+        final int testBitmapDimensionLimit = 256;
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .setBitmapDimensionLimit(testBitmapDimensionLimit)
+                .build();
+        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
+
+        // Using copy constructor, unset the limit by passing zero to the limit.
+        MediaMetadata copiedMetadataWithLimitUnset = new MediaMetadata.Builder()
+                .setBitmapDimensionLimit(0)
+                .build();
+        assertEquals(0, copiedMetadataWithLimitUnset.getBitmapDimensionLimit());
+    }
+
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index 905cb60..4312ffa 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -577,10 +577,26 @@
             MediaUtils.skipTest("no camera");
             return;
         }
+
+        int width;
+        int height;
+
         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
         mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
+        // Try to get camera profile for QUALITY_LOW; if unavailable,
+        // set the video size to default value.
+        CamcorderProfile profile = CamcorderProfile.get(
+                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
+        if (profile != null) {
+            width = profile.videoFrameWidth;
+            height = profile.videoFrameHeight;
+        } else {
+            width = VIDEO_WIDTH;
+            height = VIDEO_HEIGHT;
+        }
+        mMediaRecorder.setVideoSize(width, height);
         mMediaRecorder.setOutputFile(mOutFile);
         long maxFileSize = MAX_FILE_SIZE * 10;
         recordMedia(maxFileSize, mOutFile);
@@ -993,6 +1009,8 @@
         }
         long fileSize = 128 * 1024;
         long tolerance = 50 * 1024;
+        int width;
+        int height;
         List<String> recordFileList = new ArrayList<String>();
         mFileIndex = 0;
 
@@ -1098,7 +1116,18 @@
         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
-        mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
+        // Try to get camera profile for QUALITY_LOW; if unavailable,
+        // set the video size to default value.
+        CamcorderProfile profile = CamcorderProfile.get(
+                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
+        if (profile != null) {
+            width = profile.videoFrameWidth;
+            height = profile.videoFrameHeight;
+        } else {
+            width = VIDEO_WIDTH;
+            height = VIDEO_HEIGHT;
+        }
+        mMediaRecorder.setVideoSize(width, height);
         mMediaRecorder.setVideoEncodingBitRate(256000);
         mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
         mMediaRecorder.setMaxFileSize(fileSize);
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerTest.java b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
index 1387493..9721179 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
@@ -26,6 +26,7 @@
 import android.media.MediaScannerConnection;
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
@@ -41,6 +42,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.FileCopyHelper;
 import com.android.compatibility.common.util.PollingCheck;
 
@@ -51,6 +53,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.reflect.Method;
 import java.nio.charset.StandardCharsets;
 
 @Presubmit
@@ -635,16 +638,30 @@
         }
     }
 
-    public static void startMediaScan() {
-        new Thread(() -> {
+    private static void scanVolume() {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
             MediaStore.scanVolume(InstrumentationRegistry.getTargetContext().getContentResolver(),
                     MediaStore.VOLUME_EXTERNAL_PRIMARY);
-        }).start();
+        } else {
+            // on Q, scanVolume(Context, String path) should be used
+            try {
+                Method scanVolumeMethod = MediaStore.class
+                    .getMethod("scanVolume", Context.class, File.class);
+                scanVolumeMethod.invoke(null,
+                        InstrumentationRegistry.getTargetContext(),
+                        Environment.getExternalStorageDirectory());
+            } catch (Exception ex) {
+                fail("could not find scanVolume method" + ex);
+            }
+        }
+    }
+
+    public static void startMediaScan() {
+        new Thread(() -> { scanVolume(); }).start();
     }
 
     public static void startMediaScanAndWait() {
-        MediaStore.scanVolume(InstrumentationRegistry.getTargetContext().getContentResolver(),
-                MediaStore.VOLUME_EXTERNAL_PRIMARY);
+        scanVolume();
     }
 
     private void checkMediaScannerConnection() {
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTest.java b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
index 5cf1d3b..cf3ebff 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
@@ -16,6 +16,14 @@
 package android.media.cts;
 
 import static android.media.AudioAttributes.USAGE_GAME;
+import static android.media.cts.MediaSessionTestService.KEY_EXPECTED_QUEUE_SIZE;
+import static android.media.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS;
+import static android.media.cts.MediaSessionTestService.KEY_SESSION_TOKEN;
+import static android.media.cts.MediaSessionTestService.STEP_CHECK;
+import static android.media.cts.MediaSessionTestService.STEP_CLEAN_UP;
+import static android.media.cts.MediaSessionTestService.STEP_SET_UP;
+import static android.media.cts.MediaSessionTestService.TEST_SERIES_OF_SET_QUEUE;
+import static android.media.cts.MediaSessionTestService.TEST_SET_QUEUE_WITH_LARGE_NUMBER_OF_ITEMS;
 import static android.media.cts.Utils.compareRemoteUserInfo;
 
 import android.app.PendingIntent;
@@ -88,7 +96,10 @@
     @Override
     protected void tearDown() throws Exception {
         // It is OK to call release() twice.
-        mSession.release();
+        if (mSession != null) {
+            mSession.release();
+            mSession = null;
+        }
         super.tearDown();
     }
 
@@ -270,7 +281,8 @@
      * Test whether media button receiver can be a explicit broadcast receiver.
      */
     public void testSetMediaButtonReceiver_broadcastReceiver() throws Exception {
-        Intent intent = new Intent(mContext.getApplicationContext(), MediaButtonReceiver.class);
+        Intent intent = new Intent(mContext.getApplicationContext(),
+                MediaButtonBroadcastReceiver.class);
         PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
 
         // Play a sound so this session can get the priority.
@@ -287,7 +299,7 @@
         int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
         try {
             CountDownLatch latch = new CountDownLatch(2);
-            MediaButtonReceiver.setCallback((keyEvent) -> {
+            MediaButtonBroadcastReceiver.setCallback((keyEvent) -> {
                 assertEquals(keyCode, keyEvent.getKeyCode());
                 switch ((int) latch.getCount()) {
                     case 2:
@@ -305,7 +317,51 @@
 
             assertTrue(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
         } finally {
-            MediaButtonReceiver.setCallback(null);
+            MediaButtonBroadcastReceiver.setCallback(null);
+        }
+    }
+
+    /**
+     * Test whether media button receiver can be a explicit service.
+     */
+    public void testSetMediaButtonReceiver_service() throws Exception {
+        Intent intent = new Intent(mContext.getApplicationContext(),
+                MediaButtonReceiverService.class);
+        PendingIntent pi = PendingIntent.getService(mContext, 0, intent, 0);
+
+        // Play a sound so this session can get the priority.
+        Utils.assertMediaPlaybackStarted(getContext());
+
+        // Sets the media button receiver. Framework would try to keep the pending intent in the
+        // persistent store.
+        mSession.setMediaButtonReceiver(pi);
+
+        // Call explicit release, so change in the media key event session can be notified with the
+        // pending intent.
+        mSession.release();
+
+        int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
+        try {
+            CountDownLatch latch = new CountDownLatch(2);
+            MediaButtonReceiverService.setCallback((keyEvent) -> {
+                assertEquals(keyCode, keyEvent.getKeyCode());
+                switch ((int) latch.getCount()) {
+                    case 2:
+                        assertEquals(KeyEvent.ACTION_DOWN, keyEvent.getAction());
+                        break;
+                    case 1:
+                        assertEquals(KeyEvent.ACTION_UP, keyEvent.getAction());
+                        break;
+                }
+                latch.countDown();
+            });
+            // Also try to dispatch media key event.
+            // System would try to dispatch event.
+            simulateMediaKeyInput(keyCode);
+
+            assertTrue(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            MediaButtonReceiverService.setCallback(null);
         }
     }
 
@@ -568,15 +624,23 @@
         // Start a media playback for this app to receive media key events.
         Utils.assertMediaPlaybackStarted(getContext());
 
-        MediaSession anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
-        mSession.release();
-        anotherSession.release();
+        MediaSession anotherSession = null;
+        try {
+            anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+            mSession.release();
+            anotherSession.release();
 
-        // Try release with the different order.
-        mSession = new MediaSession(getContext(), TEST_SESSION_TAG);
-        anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
-        anotherSession.release();
-        mSession.release();
+            // Try release with the different order.
+            mSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+            anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+            anotherSession.release();
+            mSession.release();
+        } finally {
+            if (anotherSession != null) {
+                anotherSession.release();
+                anotherSession = null;
+            }
+        }
     }
 
     // This uses public APIs to dispatch key events, so sessions would consider this as
@@ -652,12 +716,17 @@
         Bundle sessionInfo = new Bundle();
         sessionInfo.putParcelable(testKey, customParcelable);
 
+        MediaSession session = null;
         try {
-            MediaSession session = new MediaSession(
+            session = new MediaSession(
                     mContext, "testSessionInfoWithCustomParcelable", sessionInfo);
             fail("Custom Parcelable shouldn't be accepted!");
         } catch (IllegalArgumentException e) {
             // Expected
+        } finally {
+            if (session != null) {
+                session.release();
+            }
         }
     }
 
@@ -686,10 +755,11 @@
      * does not decrement current session count multiple times.
      */
     public void testSessionCreationLimitWithMediaSessionRelease() {
-        MediaSession sessionToReleaseMultipleTimes = new MediaSession(
-                mContext, "testSessionCreationLimitWithMediaSessionRelease");
         List<MediaSession> sessions = new ArrayList<>();
+        MediaSession sessionToReleaseMultipleTimes = null;
         try {
+            sessionToReleaseMultipleTimes = new MediaSession(
+                    mContext, "testSessionCreationLimitWithMediaSessionRelease");
             for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) {
                 sessions.add(new MediaSession(
                         mContext, "testSessionCreationLimitWithMediaSessionRelease"));
@@ -703,6 +773,9 @@
             for (MediaSession session : sessions) {
                 session.release();
             }
+            if (sessionToReleaseMultipleTimes != null) {
+                sessionToReleaseMultipleTimes.release();
+            }
         }
     }
 
@@ -716,8 +789,9 @@
                 sessions.add(new MediaSession(
                         mContext, "testSessionCreationLimitWithMediaSession2Release"));
 
-                MediaSession2 session2 = new MediaSession2.Builder(mContext).build();
-                session2.close();
+                try (MediaSession2 session2 = new MediaSession2.Builder(mContext).build()) {
+                    // Do nothing
+                }
             }
             fail("The number of session should be limited!");
         } catch (RuntimeException e) {
@@ -730,6 +804,55 @@
     }
 
     /**
+     * Check that a series of {@link MediaSession#setQueue} does not break {@link MediaController}
+     * on the remote process due to binder buffer overflow.
+     */
+    public void testSeriesOfSetQueue() throws Exception {
+        int numberOfCalls = 100;
+        int queueSize = 1_000;
+        List<QueueItem> queue = new ArrayList<>();
+        for (int id = 0; id < queueSize; id++) {
+            MediaDescription description = new MediaDescription.Builder()
+                    .setMediaId(Integer.toString(id)).build();
+            queue.add(new QueueItem(description, id));
+        }
+
+        try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
+                MediaSessionTestService.class, TEST_SERIES_OF_SET_QUEUE)) {
+            Bundle args = new Bundle();
+            args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
+            args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * queueSize);
+            invoker.run(STEP_SET_UP, args);
+            for (int i = 0; i < numberOfCalls; i++) {
+                mSession.setQueue(queue);
+            }
+            invoker.run(STEP_CHECK);
+            invoker.run(STEP_CLEAN_UP);
+        }
+    }
+
+    public void testSetQueueWithLargeNumberOfItems() throws Exception {
+        int queueSize = 1_000_000;
+        List<QueueItem> queue = new ArrayList<>();
+        for (int id = 0; id < queueSize; id++) {
+            MediaDescription description = new MediaDescription.Builder()
+                    .setMediaId(Integer.toString(id)).build();
+            queue.add(new QueueItem(description, id));
+        }
+
+        try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
+                MediaSessionTestService.class, TEST_SET_QUEUE_WITH_LARGE_NUMBER_OF_ITEMS)) {
+            Bundle args = new Bundle();
+            args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
+            args.putInt(KEY_EXPECTED_QUEUE_SIZE, queueSize);
+            invoker.run(STEP_SET_UP, args);
+            mSession.setQueue(queue);
+            invoker.run(STEP_CHECK);
+            invoker.run(STEP_CLEAN_UP);
+        }
+    }
+
+    /**
      * Verifies that a new session hasn't had any configuration bits set yet.
      *
      * @param controller The controller for the session
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTestService.java b/tests/tests/media/src/android/media/cts/MediaSessionTestService.java
new file mode 100644
index 0000000..1b82872
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaSessionTestService.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.Nullable;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MediaSessionTestService extends RemoteService {
+    public static final int TEST_SERIES_OF_SET_QUEUE = 0;
+    public static final int TEST_SET_QUEUE_WITH_LARGE_NUMBER_OF_ITEMS = 1;
+
+    public static final int STEP_SET_UP = 0;
+    public static final int STEP_CHECK = 1;
+    public static final int STEP_CLEAN_UP = 2;
+
+    public static final String KEY_SESSION_TOKEN = "sessionToken";
+    public static final String KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS = "expectedTotalNumberOfItems";
+    public static final String KEY_EXPECTED_QUEUE_SIZE = "expectedQueueSize";
+
+    private MediaController mMediaController;
+    private MediaController.Callback mMediaControllerCallback;
+    private CountDownLatch mAllItemsNotified;
+    private CountDownLatch mQueueNotified;
+
+    private void testSeriesOfSetQueue_setUp(Bundle args) {
+        MediaSession.Token token = args.getParcelable(KEY_SESSION_TOKEN);
+        int expectedTotalNumberOfItems = args.getInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS);
+
+        mAllItemsNotified = new CountDownLatch(1);
+        AtomicInteger numberOfItems = new AtomicInteger();
+        mMediaControllerCallback = new MediaController.Callback() {
+            @Override
+            public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+                if (queue != null) {
+                    if (numberOfItems.addAndGet(queue.size()) >= expectedTotalNumberOfItems) {
+                        mAllItemsNotified.countDown();
+                    }
+                }
+            }
+        };
+        mMediaController = new MediaController(this, token);
+        mMediaController.registerCallback(mMediaControllerCallback,
+                new Handler(Looper.getMainLooper()));
+    }
+
+    private void testSeriesOfSetQueue_check() throws Exception {
+        assertTrue(mAllItemsNotified.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSeriesOfSetQueue_cleanUp() {
+        mMediaController.unregisterCallback(mMediaControllerCallback);
+        mMediaController = null;
+        mMediaControllerCallback = null;
+        mAllItemsNotified = null;
+    }
+
+    private void testSetQueueWithLargeNumberOfItems_setUp(Bundle args) {
+        MediaSession.Token token = args.getParcelable(KEY_SESSION_TOKEN);
+        int expectedQueueSize = args.getInt(KEY_EXPECTED_QUEUE_SIZE);
+
+        mQueueNotified = new CountDownLatch(1);
+        mMediaControllerCallback = new MediaController.Callback() {
+            @Override
+            public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+                if (queue != null && queue.size() == expectedQueueSize) {
+                    mQueueNotified.countDown();
+                }
+            }
+        };
+        mMediaController = new MediaController(this, token);
+        mMediaController.registerCallback(mMediaControllerCallback,
+                new Handler(Looper.getMainLooper()));
+    }
+
+    private void testSetQueueWithLargeNumberOfItems_check() throws Exception {
+        assertTrue(mQueueNotified.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSetQueueWithLargeNumberOfItems_cleanUp() {
+        mMediaController.unregisterCallback(mMediaControllerCallback);
+        mMediaController = null;
+        mMediaControllerCallback = null;
+        mQueueNotified = null;
+    }
+
+    @Override
+    public void onRun(int testId, int step, @Nullable Bundle args) throws Exception {
+        if (testId == TEST_SERIES_OF_SET_QUEUE) {
+            if (step == STEP_SET_UP) {
+                testSeriesOfSetQueue_setUp(args);
+            } else if (step == STEP_CHECK) {
+                testSeriesOfSetQueue_check();
+            } else if (step == STEP_CLEAN_UP) {
+                testSeriesOfSetQueue_cleanUp();
+            } else {
+                throw new IllegalArgumentException("Unknown step=" + step);
+            }
+        } else if (testId == TEST_SET_QUEUE_WITH_LARGE_NUMBER_OF_ITEMS) {
+            if (step == STEP_SET_UP) {
+                testSetQueueWithLargeNumberOfItems_setUp(args);
+            } else if (step == STEP_CHECK) {
+                testSetQueueWithLargeNumberOfItems_check();
+            } else if (step == STEP_CLEAN_UP) {
+                testSetQueueWithLargeNumberOfItems_cleanUp();
+            } else {
+                throw new IllegalArgumentException("Unknown step=" + step);
+            }
+
+        } else {
+            throw new IllegalArgumentException("Unknown testId=" + testId);
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/RemoteService.java b/tests/tests/media/src/android/media/cts/RemoteService.java
new file mode 100644
index 0000000..0d98251
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/RemoteService.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Base class for a service that runs on a remote process. The service that extends this class must
+ * be added to AndroidManifest.xml with "android:process" attribute to be run on a separate process.
+ */
+public abstract class RemoteService extends Service {
+    private static final String TAG = "RemoteService";
+    public static final long TIMEOUT_MS = 10_000;
+
+    private RemoteServiceStub mBinder;
+    private HandlerThread mHandlerThread;
+    private volatile Handler mHandler;
+
+    @Override
+    public void onCreate() {
+        mBinder = new RemoteServiceStub();
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+    }
+
+    @Override
+    public void onDestroy() {
+        mHandlerThread.quitSafely();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /**
+     * Called by {@link Invoker#run}. It will be run on a dedicated {@link HandlerThread}.
+     *
+     * @param testId id of the test case
+     * @param step the step of a command to run
+     * @param args optional arguments
+     * @throws Exception if any
+     */
+    public abstract void onRun(int testId, int step, @Nullable Bundle args) throws Exception;
+
+    private boolean runOnHandlerSync(TestRunnable runnable) {
+        CountDownLatch latch = new CountDownLatch(1);
+        AtomicReference<Throwable> throwable = new AtomicReference<>();
+        mHandler.post(() -> {
+            try {
+                runnable.run();
+            } catch (Throwable th) {
+                throwable.set(th);
+                Log.e(TAG, "Error while running TestRunnable", th);
+            }
+            latch.countDown();
+        });
+        try {
+            boolean done = latch.await(TIMEOUT_MS, MILLISECONDS);
+            return done && throwable.get() == null;
+        } catch (InterruptedException ex) {
+            Log.w(TAG, ex);
+            return false;
+        }
+    }
+
+    private interface TestRunnable {
+        void run() throws Exception;
+    }
+
+    private class RemoteServiceStub extends IRemoteService.Stub {
+        @Override
+        public boolean run(int testId, int step, Bundle args) throws RemoteException {
+            return runOnHandlerSync(() -> onRun(testId, step, args));
+        }
+    }
+
+    /**
+     * A class to run commands on a {@link RemoteService} for a test case.
+     */
+    public static class Invoker implements Closeable {
+        private static final String ASSERTION_MESSAGE =
+                "Failed on remote service. See logcat TAG=" + TAG + " for detail.";
+
+        private final Context mContext;
+        private final int mTestId;
+        private final CountDownLatch mConnectionLatch;
+        private final ServiceConnection mServiceConnection;
+        private IRemoteService mBinder;
+
+        /**
+         * Creates an instance and connects to the remote service.
+         *
+         * @param context the context
+         * @param serviceClass the class of remote service
+         * @param testId id of the test case
+         * @throws InterruptedException if the thread is interrupted while waiting for connection
+         */
+        public Invoker(@NonNull Context context,
+                @NonNull Class<? extends RemoteService> serviceClass, int testId)
+                throws InterruptedException {
+            mContext = context;
+            mTestId = testId;
+            mConnectionLatch = new CountDownLatch(1);
+            mServiceConnection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    mBinder = IRemoteService.Stub.asInterface(service);
+                    mConnectionLatch.countDown();
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    mBinder = null;
+                }
+            };
+
+            Intent intent = new Intent(mContext, serviceClass);
+            mContext.bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
+            assertTrue("Failed to bind to service " + serviceClass,
+                    mConnectionLatch.await(TIMEOUT_MS, MILLISECONDS));
+        }
+
+        /**
+         * Disconnects from the remote service.
+         */
+        @Override
+        public void close() {
+            mContext.unbindService(mServiceConnection);
+        }
+
+        /**
+         * Invokes {@link #onRun} on the remote service without optional arguments.
+         *
+         * @param step the step of a command to run
+         * @throws RemoteException if binder throws exception
+         */
+        public void run(int step) throws RemoteException {
+            run(step, null);
+        }
+
+        /**
+         * Invokes {@link #onRun} on the remote service.
+         *
+         * @param step the step of a command to run
+         * @param args optional arguments
+         * @throws RemoteException if binder throws exception
+         */
+        public void run(int step, @Nullable Bundle args) throws RemoteException {
+            assertTrue(ASSERTION_MESSAGE, mBinder.run(mTestId, step, args));
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/StubMediaBrowserService.java b/tests/tests/media/src/android/media/cts/StubMediaBrowserService.java
index 9190d10..f9ec34c 100644
--- a/tests/tests/media/src/android/media/cts/StubMediaBrowserService.java
+++ b/tests/tests/media/src/android/media/cts/StubMediaBrowserService.java
@@ -16,6 +16,7 @@
 
 package android.media.cts;
 
+import android.annotation.NonNull;
 import android.media.MediaDescription;
 import android.media.browse.MediaBrowser.MediaItem;
 import android.media.session.MediaSession;
@@ -27,7 +28,9 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Stub implementation of (@link android.service.media.MediaBrowserService}.
@@ -55,6 +58,7 @@
     private Result<List<MediaItem>> mPendingLoadChildrenResult;
     private Result<MediaItem> mPendingLoadItemResult;
     private Bundle mPendingRootHints;
+    private final Map<String, List<MediaItem>> mChildrenMap = new HashMap<>();
 
     public static void clearBrowserInfo() {
         sBrowserInfo = null;
@@ -102,6 +106,8 @@
             result.detach();
         } else if (MEDIA_ID_INVALID.equals(parentMediaId)) {
             result.sendResult(null);
+        } else if (mChildrenMap.containsKey(parentMediaId)) {
+            result.sendResult(mChildrenMap.get(parentMediaId));
         }
     }
 
@@ -127,6 +133,14 @@
         super.onLoadItem(itemId, result);
     }
 
+    public void putChildrenToMap(@NonNull String parentMediaId, @NonNull List<MediaItem> children) {
+        mChildrenMap.put(parentMediaId, children);
+    }
+
+    public void removeChildrenFromMap(@NonNull String parentMediaId) {
+        mChildrenMap.remove(parentMediaId);
+    }
+
     public void sendDelayedNotifyChildrenChanged() {
         if (mPendingLoadChildrenResult != null) {
             mPendingLoadChildrenResult.sendResult(Collections.<MediaItem>emptyList());
diff --git a/tests/tests/mediastress/AndroidManifest.xml b/tests/tests/mediastress/AndroidManifest.xml
index 81d8a00..157cd3c 100644
--- a/tests/tests/mediastress/AndroidManifest.xml
+++ b/tests/tests/mediastress/AndroidManifest.xml
@@ -15,41 +15,42 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.mediastress.cts">
+     package="android.mediastress.cts">
 
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
 
-    <application
-        android:requestLegacyExternalStorage="true">
-        <uses-library android:name="android.test.runner" />
+    <application android:requestLegacyExternalStorage="true">
+        <uses-library android:name="android.test.runner"/>
         <activity android:label="@string/app_name"
-                android:name="android.mediastress.cts.MediaFrameworkTest"
-                android:screenOrientation="landscape"
-                android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
+             android:name="android.mediastress.cts.MediaFrameworkTest"
+             android:screenOrientation="landscape"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name="android.mediastress.cts.NativeMediaActivity"
-                  android:label="NativeMedia" />
+             android:label="NativeMedia"/>
     </application>
 
-    <uses-sdk android:minSdkVersion="29"   android:targetSdkVersion="29" />
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="android.mediastress.cts"
-            android:label="Media stress tests InstrumentationRunner" >
+         android:targetPackage="android.mediastress.cts"
+         android:label="Media stress tests InstrumentationRunner">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/midi/AndroidManifest.xml b/tests/tests/midi/AndroidManifest.xml
index c82bb32..6e7bce9 100755
--- a/tests/tests/midi/AndroidManifest.xml
+++ b/tests/tests/midi/AndroidManifest.xml
@@ -16,33 +16,32 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.midi.cts">
+     package="android.midi.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
-    <uses-feature android:name="android.software.midi" android:required="true"/>
+    <uses-feature android:name="android.software.midi"
+         android:required="true"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name="com.android.midi.MidiEchoTestService"
-                android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+             android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.midi.MidiDeviceService" />
+                <action android:name="android.media.midi.MidiDeviceService"/>
             </intent-filter>
             <meta-data android:name="android.media.midi.MidiDeviceService"
-                android:resource="@xml/echo_device_info" />
+                 android:resource="@xml/echo_device_info"/>
         </service>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS MIDI tests"
-        android:targetPackage="android.midi.cts" >
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS MIDI tests"
+         android:targetPackage="android.midi.cts">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/midi/TEST_MAPPING b/tests/tests/midi/TEST_MAPPING
new file mode 100644
index 0000000..af15405
--- /dev/null
+++ b/tests/tests/midi/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMidiTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/multiuser/TEST_MAPPING b/tests/tests/multiuser/TEST_MAPPING
new file mode 100644
index 0000000..4c38300
--- /dev/null
+++ b/tests/tests/multiuser/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMultiUserTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/nativemedia/sl/TEST_MAPPING b/tests/tests/nativemedia/sl/TEST_MAPPING
new file mode 100644
index 0000000..e2f65df
--- /dev/null
+++ b/tests/tests/nativemedia/sl/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNativeMediaSlTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/nativemedia/xa/TEST_MAPPING b/tests/tests/nativemedia/xa/TEST_MAPPING
new file mode 100644
index 0000000..e73b66e
--- /dev/null
+++ b/tests/tests/nativemedia/xa/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNativeMediaXaTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/nativemidi/AndroidManifest.xml b/tests/tests/nativemidi/AndroidManifest.xml
index 1275ea0..f89d012 100755
--- a/tests/tests/nativemidi/AndroidManifest.xml
+++ b/tests/tests/nativemidi/AndroidManifest.xml
@@ -16,38 +16,37 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.nativemidi.cts">
+     package="android.nativemidi.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
-    <uses-feature android:name="android.software.midi" android:required="true"/>
+    <uses-feature android:name="android.software.midi"
+         android:required="true"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name="com.android.midi.MidiEchoTestService"
-            android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+             android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.midi.MidiDeviceService" />
+                <action android:name="android.media.midi.MidiDeviceService"/>
             </intent-filter>
             <meta-data android:name="android.media.midi.MidiDeviceService"
-                android:resource="@xml/echo_device_info" />
+                 android:resource="@xml/echo_device_info"/>
         </service>
 
         <!--
         <activity android:name="android.nativemidi.cts.NativeMidiEchoTest"
-                  android:label="NativeMidiEchoTest"/>
-        -->
+                              android:label="NativeMidiEchoTest"/>
+                    -->
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS Native MIDI tests"
-        android:targetPackage="android.nativemidi.cts" >
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS Native MIDI tests"
+         android:targetPackage="android.nativemidi.cts">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/ndef/TEST_MAPPING b/tests/tests/ndef/TEST_MAPPING
new file mode 100644
index 0000000..6cd1292
--- /dev/null
+++ b/tests/tests/ndef/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNdefTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/net/api23Test/AndroidManifest.xml b/tests/tests/net/api23Test/AndroidManifest.xml
index 4889660..69ee0dd 100644
--- a/tests/tests/net/api23Test/AndroidManifest.xml
+++ b/tests/tests/net/api23Test/AndroidManifest.xml
@@ -16,7 +16,7 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.net.cts.api23test">
+     package="android.net.cts.api23test">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
@@ -26,20 +26,20 @@
     <uses-permission android:name="android.permission.INTERNET" />
 
     <application android:usesCleartextTraffic="true">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <receiver android:name=".ConnectivityReceiver">
+        <receiver android:name=".ConnectivityReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
             </intent-filter>
         </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.net.cts.api23test"
-                     android:label="CTS tests of android.net">
+         android:targetPackage="android.net.cts.api23test"
+         android:label="CTS tests of android.net">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/net/appForApi23/AndroidManifest.xml b/tests/tests/net/appForApi23/AndroidManifest.xml
index ed4cedb..158b9c4 100644
--- a/tests/tests/net/appForApi23/AndroidManifest.xml
+++ b/tests/tests/net/appForApi23/AndroidManifest.xml
@@ -16,32 +16,32 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.net.cts.appForApi23">
+     package="android.net.cts.appForApi23">
 
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <application>
-        <receiver android:name=".ConnectivityReceiver">
+        <receiver android:name=".ConnectivityReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.net.cts.appForApi23.getWifiConnectivityActionCount" />
+                <action android:name="android.net.cts.appForApi23.getWifiConnectivityActionCount"/>
             </intent-filter>
         </receiver>
 
         <activity android:name=".ConnectivityListeningActivity"
-                  android:label="ConnectivityListeningActivity"
-                  android:exported="true">
+             android:label="ConnectivityListeningActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
     </application>
 
 </manifest>
-
diff --git a/tests/tests/netpermission/internetpermission/AndroidManifest.xml b/tests/tests/netpermission/internetpermission/AndroidManifest.xml
index 23b7c0a..45ef5bd 100644
--- a/tests/tests/netpermission/internetpermission/AndroidManifest.xml
+++ b/tests/tests/netpermission/internetpermission/AndroidManifest.xml
@@ -16,12 +16,13 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.networkpermission.internetpermission.cts">
+     package="android.networkpermission.internetpermission.cts">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.networkpermission.internetpermission.cts.InternetPermissionTest"
-                  android:label="InternetPermissionTest">
+             android:label="InternetPermissionTest"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
@@ -30,21 +31,20 @@
     </application>
 
     <!--
-        The CTS stubs package cannot be used as the target application here,
-        since that requires many permissions to be set. Instead, specify this
-        package itself as the target and include any stub activities needed.
+                The CTS stubs package cannot be used as the target application here,
+                since that requires many permissions to be set. Instead, specify this
+                package itself as the target and include any stub activities needed.
 
-        This test package uses the default InstrumentationTestRunner, because
-        the InstrumentationCtsTestRunner is only available in the stubs
-        package. That runner cannot be added to this package either, since it
-        relies on hidden APIs.
-    -->
+                This test package uses the default InstrumentationTestRunner, because
+                the InstrumentationCtsTestRunner is only available in the stubs
+                package. That runner cannot be added to this package either, since it
+                relies on hidden APIs.
+            -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.networkpermission.internetpermission.cts"
-                     android:label="CTS tests for INTERNET permissions">
+         android:targetPackage="android.networkpermission.internetpermission.cts"
+         android:label="CTS tests for INTERNET permissions">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/netpermission/internetpermission/TEST_MAPPING b/tests/tests/netpermission/internetpermission/TEST_MAPPING
new file mode 100644
index 0000000..60877f4
--- /dev/null
+++ b/tests/tests/netpermission/internetpermission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetTestCasesInternetPermission"
+    }
+  ]
+}
diff --git a/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml b/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml
index a4eca82..6babe8f 100644
--- a/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml
+++ b/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml
@@ -16,20 +16,21 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.networkpermission.updatestatspermission.cts">
+     package="android.networkpermission.updatestatspermission.cts">
 
     <!--
-         This CTS test is designed to test that an unprivileged app cannot get the
-         UPDATE_DEVICE_STATS permission even if it specified it in the manifest. the
-         UPDATE_DEVICE_STATS permission is a signature|privileged permission that CTS
-         test cannot have.
-    -->
-    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
-    <uses-permission android:name="android.permission.INTERNET" />
+                 This CTS test is designed to test that an unprivileged app cannot get the
+                 UPDATE_DEVICE_STATS permission even if it specified it in the manifest. the
+                 UPDATE_DEVICE_STATS permission is a signature|privileged permission that CTS
+                 test cannot have.
+            -->
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.networkpermission.updatestatspermission.cts.UpdateStatsPermissionTest"
-                  android:label="UpdateStatsPermissionTest">
+             android:label="UpdateStatsPermissionTest"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
@@ -38,21 +39,20 @@
     </application>
 
     <!--
-        The CTS stubs package cannot be used as the target application here,
-        since that requires many permissions to be set. Instead, specify this
-        package itself as the target and include any stub activities needed.
+                The CTS stubs package cannot be used as the target application here,
+                since that requires many permissions to be set. Instead, specify this
+                package itself as the target and include any stub activities needed.
 
-        This test package uses the default InstrumentationTestRunner, because
-        the InstrumentationCtsTestRunner is only available in the stubs
-        package. That runner cannot be added to this package either, since it
-        relies on hidden APIs.
-    -->
+                This test package uses the default InstrumentationTestRunner, because
+                the InstrumentationCtsTestRunner is only available in the stubs
+                package. That runner cannot be added to this package either, since it
+                relies on hidden APIs.
+            -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.networkpermission.updatestatspermission.cts"
-                     android:label="CTS tests for UPDATE_DEVICE_STATS permissions">
+         android:targetPackage="android.networkpermission.updatestatspermission.cts"
+         android:label="CTS tests for UPDATE_DEVICE_STATS permissions">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/netpermission/updatestatspermission/TEST_MAPPING b/tests/tests/netpermission/updatestatspermission/TEST_MAPPING
new file mode 100644
index 0000000..6d6dfe0
--- /dev/null
+++ b/tests/tests/netpermission/updatestatspermission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetTestCasesUpdateStatsPermission"
+    }
+  ]
+}
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-false/TEST_MAPPING b/tests/tests/netsecpolicy/usescleartexttraffic-false/TEST_MAPPING
new file mode 100644
index 0000000..43b85e9
--- /dev/null
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-false/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecPolicyUsesCleartextTrafficFalseTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/TEST_MAPPING b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/TEST_MAPPING
new file mode 100644
index 0000000..4b9bb16
--- /dev/null
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecPolicyUsesCleartextTrafficUnspecifiedTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/TEST_MAPPING
new file mode 100644
index 0000000..49aaca1
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigAttributeTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/TEST_MAPPING
new file mode 100644
index 0000000..b4cf9c0
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigBasicDomainConfigTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/TEST_MAPPING
new file mode 100644
index 0000000..021dd45
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigPrePCleartextTrafficTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/TEST_MAPPING
new file mode 100644
index 0000000..c015f28
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigCleartextTrafficTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/TEST_MAPPING
new file mode 100644
index 0000000..1c0ace3
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigBasicDebugDisabledTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/TEST_MAPPING
new file mode 100644
index 0000000..8bfb01a
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigBasicDebugEnabledTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/TEST_MAPPING
new file mode 100644
index 0000000..9aaae09
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigDownloadManagerTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/TEST_MAPPING
new file mode 100644
index 0000000..8786f17
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigInvalidPinTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/TEST_MAPPING
new file mode 100644
index 0000000..904eda5
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigNestedDomainConfigTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/TEST_MAPPING
new file mode 100644
index 0000000..8462e93
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigResourcesSrcTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/neuralnetworks/benchmark/AndroidManifest.xml b/tests/tests/neuralnetworks/benchmark/AndroidManifest.xml
index 2a5df64..0e96960 100644
--- a/tests/tests/neuralnetworks/benchmark/AndroidManifest.xml
+++ b/tests/tests/neuralnetworks/benchmark/AndroidManifest.xml
@@ -15,21 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.nn.benchmark.cts">
+     package="com.android.nn.benchmark.cts">
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-sdk android:minSdkVersion="27"/>
 
     <application android:name=".NNAccuracyApplication">
-        <activity android:name=".NNAccuracyActivity">
+        <activity android:name=".NNAccuracyActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-            android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.nn.benchmark.cts"
-            android:label="CTS tests of NNAPI accuracy benchmark"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.nn.benchmark.cts"
+         android:label="CTS tests of NNAPI accuracy benchmark"/>
 </manifest>
diff --git a/tests/tests/neuralnetworks/tflite_delegate/Android.mk b/tests/tests/neuralnetworks/tflite_delegate/Android.mk
index 11f23c4..cbe2f06 100644
--- a/tests/tests/neuralnetworks/tflite_delegate/Android.mk
+++ b/tests/tests/neuralnetworks/tflite_delegate/Android.mk
@@ -62,7 +62,7 @@
 LOCAL_WHOLE_STATIC_LIBRARIES := CtsTfliteNnapiDelegateTests_static
 
 LOCAL_SHARED_LIBRARIES := libandroid liblog libneuralnetworks
-LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libtflite_static
+LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libgmock_ndk libtflite_static
 LOCAL_CTS_TEST_PACKAGE := android.neuralnetworks
 
 # Tag this module as a cts test artifact
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/TEST_MAPPING b/tests/tests/notificationlegacy/notificationlegacy27/TEST_MAPPING
new file mode 100644
index 0000000..b540596
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy27/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLegacyNotification27TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/opengl/AndroidManifest.xml b/tests/tests/opengl/AndroidManifest.xml
index 7b645aa..a7a09b7 100644
--- a/tests/tests/opengl/AndroidManifest.xml
+++ b/tests/tests/opengl/AndroidManifest.xml
@@ -13,61 +13,57 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.opengl.cts"
-    android:versionCode="1"
-    android:versionName="1.0" >
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.opengl.cts"
+     android:versionCode="1"
+     android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-feature android:glEsVersion="0x00020000"/>
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.opengl.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.opengl.cts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
-    <application
-        android:icon="@drawable/ic_launcher"
-        android:label="@string/app_name"
-        android:hardwareAccelerated="false" >
+    <application android:icon="@drawable/ic_launcher"
+         android:label="@string/app_name"
+         android:hardwareAccelerated="false">
 
-         <activity
-            android:label="@string/app_name"
-            android:name="android.opengl.cts.OpenGLES20ActivityOne">
+         <activity android:label="@string/app_name"
+              android:name="android.opengl.cts.OpenGLES20ActivityOne">
          </activity>
-          <activity
-            android:label="@string/app_name"
-            android:name="android.opengl.cts.OpenGLES20ActivityTwo">
+          <activity android:label="@string/app_name"
+               android:name="android.opengl.cts.OpenGLES20ActivityTwo">
          </activity>
-         <uses-library  android:name="android.test.runner" />
-         <activity
-            android:name="android.opengl.cts.OpenGLES20NativeActivityOne"
-            android:label="@string/app_name" />
-         <activity
-            android:name="android.opengl.cts.OpenGLES20NativeActivityTwo"
-            android:label="@string/app_name" />
+         <uses-library android:name="android.test.runner"/>
+         <activity android:name="android.opengl.cts.OpenGLES20NativeActivityOne"
+              android:label="@string/app_name"/>
+         <activity android:name="android.opengl.cts.OpenGLES20NativeActivityTwo"
+              android:label="@string/app_name"/>
 
          <activity android:name="android.opengl.cts.CompressedTextureCtsActivity"
-            android:label="CompressedTextureCtsActivity"
-            android:screenOrientation="nosensor"
-            android:hardwareAccelerated="true">
+              android:label="CompressedTextureCtsActivity"
+              android:screenOrientation="nosensor"
+              android:hardwareAccelerated="true"
+              android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.opengl.cts.EglConfigCtsActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize|uiMode"
-            android:hardwareAccelerated="true"/>
+             android:configChanges="keyboardHidden|orientation|screenSize|uiMode"
+             android:hardwareAccelerated="true"/>
 
         <activity android:name="android.opengl.cts.GLSurfaceViewCtsActivity"
-            android:label="GLSurfaceViewCts"
-            android:hardwareAccelerated="true"/>
+             android:label="GLSurfaceViewCts"
+             android:hardwareAccelerated="true"/>
 
         <activity android:name="android.opengl.cts.OpenGlEsVersionCtsActivity"
-            android:hardwareAccelerated="true"/>
+             android:hardwareAccelerated="true"/>
 
     </application>
 
diff --git a/tests/tests/openglperf/AndroidManifest.xml b/tests/tests/openglperf/AndroidManifest.xml
index 5ccdc1e..8573455 100644
--- a/tests/tests/openglperf/AndroidManifest.xml
+++ b/tests/tests/openglperf/AndroidManifest.xml
@@ -13,42 +13,43 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.openglperf.cts"
-    android:versionCode="1"
-    android:versionName="1.0" >
 
-    <uses-feature android:glEsVersion="0x00020000" android:required="true" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.GET_TASKS" />
-    <uses-permission android:name="android.permission.REORDER_TASKS" />
-    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.openglperf.cts"
+     android:versionCode="1"
+     android:versionName="1.0">
+
+    <uses-feature android:glEsVersion="0x00020000"
+         android:required="true"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.GET_TASKS"/>
+    <uses-permission android:name="android.permission.REORDER_TASKS"/>
+    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
 
     <!-- Two activities are used -->
-    <instrumentation
-        android:targetPackage="com.replica.replicaisland"
-        android:name="androidx.test.runner.AndroidJUnitRunner" >
+    <instrumentation android:targetPackage="com.replica.replicaisland"
+         android:name="androidx.test.runner.AndroidJUnitRunner">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
-    <instrumentation
-        android:targetPackage="android.openglperf.cts"
-        android:name="androidx.test.runner.AndroidJUnitRunner">
+    <instrumentation android:targetPackage="android.openglperf.cts"
+         android:name="androidx.test.runner.AndroidJUnitRunner">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.openglperf.cts.GlPlanetsActivity"
-		  android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name="android.openglperf.cts.TextureTestActivity"
-		  android:configChanges="keyboard|keyboardHidden|orientation|screenSize" />
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"/>
     </application>
 
 </manifest>
diff --git a/tests/tests/os/Android.bp b/tests/tests/os/Android.bp
index c3592a7..87d64d6 100644
--- a/tests/tests/os/Android.bp
+++ b/tests/tests/os/Android.bp
@@ -26,7 +26,8 @@
         "truth-prebuilt",
         "guava",
         "junit",
-        "CtsMockInputMethodLib"
+        "CtsMockInputMethodLib",
+        "hamcrest-library",
     ],
     jni_uses_platform_apis: true,
     jni_libs: [
@@ -50,6 +51,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "test_current",
     libs: [
@@ -58,4 +60,5 @@
     ],
     // Do not compress minijail policy files.
     aaptflags: ["-0 .policy"],
+    min_sdk_version : "29"
 }
diff --git a/tests/tests/os/AndroidManifest.xml b/tests/tests/os/AndroidManifest.xml
index 5a53fa5..4b565f0 100644
--- a/tests/tests/os/AndroidManifest.xml
+++ b/tests/tests/os/AndroidManifest.xml
@@ -16,153 +16,160 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.os.cts">
+     package="android.os.cts">
 
     <permission android:name="android.os.cts.permission.TEST_GRANTED"
-        android:protectionLevel="normal"
-            android:label="@string/permlab_testGranted"
-            android:description="@string/permdesc_testGranted">
-        <meta-data android:name="android.os.cts.string" android:value="foo" />
-        <meta-data android:name="android.os.cts.boolean" android:value="true" />
-        <meta-data android:name="android.os.cts.integer" android:value="100" />
-        <meta-data android:name="android.os.cts.color" android:value="#ff000000" />
-        <meta-data android:name="android.os.cts.float" android:value="100.1" />
-        <meta-data android:name="android.os.cts.reference" android:resource="@xml/metadata" />
+         android:protectionLevel="normal"
+         android:label="@string/permlab_testGranted"
+         android:description="@string/permdesc_testGranted">
+        <meta-data android:name="android.os.cts.string"
+             android:value="foo"/>
+        <meta-data android:name="android.os.cts.boolean"
+             android:value="true"/>
+        <meta-data android:name="android.os.cts.integer"
+             android:value="100"/>
+        <meta-data android:name="android.os.cts.color"
+             android:value="#ff000000"/>
+        <meta-data android:name="android.os.cts.float"
+             android:value="100.1"/>
+        <meta-data android:name="android.os.cts.reference"
+             android:resource="@xml/metadata"/>
     </permission>
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.VIBRATE" />
-    <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
     <uses-permission android:name="android.permission.READ_SMS"/>
     <uses-permission android:name="android.permission.WRITE_SMS"/>
-    <uses-permission android:name="android.permission.CALL_PHONE" />
-    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
-    <uses-permission android:name="android.permission.DEVICE_POWER" />
-    <uses-permission android:name="android.permission.POWER_SAVER" />
-    <uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM" />
-    <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" />
+    <uses-permission android:name="android.permission.CALL_PHONE"/>
+    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
+    <uses-permission android:name="android.permission.DEVICE_POWER"/>
+    <uses-permission android:name="android.permission.POWER_SAVER"/>
+    <uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
+    <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-    <uses-permission android:name="android.os.cts.permission.TEST_GRANTED" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.os.cts.permission.TEST_GRANTED"/>
 
-    <application
-            android:usesCleartextTraffic="true"
-            android:requestLegacyExternalStorage="true">
+    <application android:usesCleartextTraffic="true"
+         android:requestLegacyExternalStorage="true">
         <activity android:name="android.os.cts.LaunchpadActivity"
-                  android:configChanges="keyboardHidden|orientation|screenSize"
-                  android:multiprocess="true">
+             android:configChanges="keyboardHidden|orientation|screenSize"
+             android:multiprocess="true">
         </activity>
 
         <activity android:name="android.os.cts.AliasActivityStub">
             <meta-data android:name="android.os.alias"
-                android:resource="@xml/alias" />
+                 android:resource="@xml/alias"/>
         </activity>
 
         <activity android:name="android.os.cts.CountDownTimerTestStub"
-            android:label="CountDownTimerTestStub">
+             android:label="CountDownTimerTestStub"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.os.cts.SimpleTestActivity" />
+        <activity android:name="android.os.cts.SimpleTestActivity"/>
 
-        <service
-            android:name="android.os.cts.ParcelFileDescriptorPeer$Red"
-            android:process=":red"
-            android:exported="true" />
-        <service
-            android:name="android.os.cts.ParcelFileDescriptorPeer$Blue"
-            android:process=":blue"
-            android:exported="true" />
-        <service
-            android:name="android.os.cts.CrossProcessExceptionService"
-            android:process=":green"
-            android:exported="true" />
-        <service
-            android:name="android.os.cts.SharedMemoryService"
-            android:process=":sharedmem"
-            android:exported="false" />
-        <service
-            android:name="android.os.cts.ParcelExceptionService"
-            android:process=":remote"
-            android:exported="true" />
-        <service
-            android:name="android.os.cts.ParcelTest$ParcelObjectFreeService"
-            android:process=":remote"
-            android:exported="true" />
+        <service android:name="android.os.cts.ParcelFileDescriptorPeer$Red"
+             android:process=":red"
+             android:exported="true"/>
+        <service android:name="android.os.cts.ParcelFileDescriptorPeer$Blue"
+             android:process=":blue"
+             android:exported="true"/>
+        <service android:name="android.os.cts.CrossProcessExceptionService"
+             android:process=":green"
+             android:exported="true"/>
+        <service android:name="android.os.cts.SharedMemoryService"
+             android:process=":sharedmem"
+             android:exported="false"/>
+        <service android:name="android.os.cts.ParcelExceptionService"
+             android:process=":remote"
+             android:exported="true"/>
+        <service android:name="android.os.cts.ParcelTest$ParcelObjectFreeService"
+             android:process=":remote"
+             android:exported="true"/>
 
-        <service android:name="android.os.cts.LocalService">
+        <service android:name="android.os.cts.LocalService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.os.cts.activity.SERVICE_LOCAL" />
+                <action android:name="android.os.cts.activity.SERVICE_LOCAL"/>
             </intent-filter>
-            <meta-data android:name="android.os.cts.string" android:value="foo" />
-            <meta-data android:name="android.os.cts.boolean" android:value="true" />
-            <meta-data android:name="android.os.cts.integer" android:value="100" />
-            <meta-data android:name="android.os.cts.color" android:value="#ff000000" />
-            <meta-data android:name="android.os.cts.float" android:value="100.1" />
-            <meta-data android:name="android.os.cts.reference" android:resource="@xml/metadata" />
+            <meta-data android:name="android.os.cts.string"
+                 android:value="foo"/>
+            <meta-data android:name="android.os.cts.boolean"
+                 android:value="true"/>
+            <meta-data android:name="android.os.cts.integer"
+                 android:value="100"/>
+            <meta-data android:name="android.os.cts.color"
+                 android:value="#ff000000"/>
+            <meta-data android:name="android.os.cts.float"
+                 android:value="100.1"/>
+            <meta-data android:name="android.os.cts.reference"
+                 android:resource="@xml/metadata"/>
         </service>
 
         <service android:name="android.os.cts.LocalGrantedService"
-             android:permission="android.os.cts.permission.TEST_GRANTED">
+             android:permission="android.os.cts.permission.TEST_GRANTED"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.os.cts.activity.SERVICE_LOCAL_GRANTED" />
+                <action android:name="android.os.cts.activity.SERVICE_LOCAL_GRANTED"/>
             </intent-filter>
         </service>
 
         <service android:name="android.os.cts.LocalDeniedService"
-               android:permission="android.os.cts.permission.TEST_DENIED">
+             android:permission="android.os.cts.permission.TEST_DENIED"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.os.cts.activity.SERVICE_LOCAL_DENIED" />
+                <action android:name="android.os.cts.activity.SERVICE_LOCAL_DENIED"/>
             </intent-filter>
         </service>
 
 
         <service android:name="android.os.cts.EmptyService"
-            android:process=":remote">
+             android:process=":remote"
+             android:exported="true">
             <intent-filter>
-                <action
-                    android:name="android.os.cts.IEmptyService" />
-                <action
-                    android:name="android.os.REMOTESERVICE" />
+                <action android:name="android.os.cts.IEmptyService"/>
+                <action android:name="android.os.REMOTESERVICE"/>
             </intent-filter>
         </service>
 
         <service android:name="android.os.cts.CtsRemoteService"
-            android:process=":remote">
+             android:process=":remote"
+             android:exported="true">
             <intent-filter>
-                <action
-                    android:name="android.os.cts.ISecondary" />
-                <action
-                    android:name="android.os.REMOTESERVICE" />
+                <action android:name="android.os.cts.ISecondary"/>
+                <action android:name="android.os.REMOTESERVICE"/>
             </intent-filter>
         </service>
 
         <service android:name="android.os.cts.SeccompTest$IsolatedService"
-                android:isolatedProcess="true">
+             android:isolatedProcess="true">
         </service>
 
         <service android:name="android.os.cts.MessengerService"
-                android:process=":messengerService">
+             android:process=":messengerService">
         </service>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.os.cts"
-                     android:label="CTS tests of android.os">
+         android:targetPackage="android.os.cts"
+         android:label="CTS tests of android.os">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/os/CtsOsTestCases.xml b/tests/tests/os/CtsOsTestCases.xml
index 72902e68..96787e7 100644
--- a/tests/tests/os/CtsOsTestCases.xml
+++ b/tests/tests/os/CtsOsTestCases.xml
@@ -18,20 +18,11 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsOsTestCases.apk" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="force-install-mode" value="FULL"/>
-        <option name="test-file-name" value="CtsMockInputMethod.apk" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-        <option name="force-skip-system-props" value="true" />
-        <option name="screen-always-on" value="on" />
-    </target_preparer>
-
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.os.cts" />
         <option name="runtime-hint" value="3m15s" />
diff --git a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
index 44a5444..ce0e630 100644
--- a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
+++ b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
@@ -16,6 +16,8 @@
 
 package android.os.cts
 
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING
 import android.app.Instrumentation
 import android.content.Context
 import android.content.Intent
@@ -38,6 +40,7 @@
 import com.android.compatibility.common.util.MatcherUtils.hasTextThat
 import com.android.compatibility.common.util.SystemUtil
 import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.compatibility.common.util.ThrowingSupplier
 import com.android.compatibility.common.util.UiAutomatorUtils
@@ -50,6 +53,8 @@
 import org.hamcrest.CoreMatchers.containsStringIgnoringCase
 import org.hamcrest.CoreMatchers.equalTo
 import org.hamcrest.Matcher
+import org.hamcrest.Matchers.greaterThan
+import org.hamcrest.Matchers.lessThanOrEqualTo
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertThat
@@ -86,20 +91,14 @@
 
     @Before
     fun setup() {
-        // Kill Permission Controller
-        assertThat(
-                runShellCommand("killall " +
-                        context.packageManager.permissionControllerPackageName),
-                equalTo(""))
-
         // Collapse notifications
         assertThat(
-                runShellCommand("cmd statusbar collapse"),
+                runShellCommandOrThrow("cmd statusbar collapse"),
                 equalTo(""))
 
         // Wake up the device
-        runShellCommand("input keyevent KEYCODE_WAKEUP")
-        runShellCommand("input keyevent 82")
+        runShellCommandOrThrow("input keyevent KEYCODE_WAKEUP")
+        runShellCommandOrThrow("input keyevent 82")
     }
 
     @AppModeFull(reason = "Uses separate apps for testing")
@@ -113,9 +112,7 @@
                 eventually {
                     assertPermission(PERMISSION_GRANTED)
                 }
-                goBack()
-                goHome()
-                goBack()
+                killDummyApp()
                 Thread.sleep(5)
 
                 // Run
@@ -125,7 +122,7 @@
                 eventually {
                     assertPermission(PERMISSION_DENIED)
                 }
-                runShellCommand("cmd statusbar expand-notifications")
+                runShellCommandOrThrow("cmd statusbar expand-notifications")
                 waitFindObject(By.textContains("unused app"))
                         .click()
                 waitFindObject(By.text(APK_PACKAGE_NAME))
@@ -145,7 +142,7 @@
                 eventually {
                     assertPermission(PERMISSION_GRANTED)
                 }
-                goHome()
+                killDummyApp()
                 Thread.sleep(5)
 
                 // Run
@@ -170,9 +167,7 @@
                         assertPermission(PERMISSION_GRANTED, APK_PACKAGE_NAME_2)
                     }
 
-                    goBack()
-                    goHome()
-                    goBack()
+                    killDummyApp(APK_PACKAGE_NAME_2)
 
                     startApp()
                     clickPermissionAllow()
@@ -180,9 +175,7 @@
                         assertPermission(PERMISSION_GRANTED)
                     }
 
-                    goBack()
-                    goHome()
-                    goBack()
+                    killDummyApp()
                     Thread.sleep(20)
 
                     // Run
@@ -245,13 +238,12 @@
                 goToPermissions()
                 click("Calendar")
                 click("Allow")
-                Thread.sleep(500)
-                goBack()
-                goBack()
-                goBack()
                 eventually {
                     assertPermission(PERMISSION_GRANTED)
                 }
+                goBack()
+                goBack()
+                goBack()
 
                 // Run
                 runAutoRevoke()
@@ -293,8 +285,14 @@
     }
 
     private fun runAutoRevoke() {
-        runShellCommand("cmd jobscheduler run -u 0 " +
-                "-f ${context.packageManager.permissionControllerPackageName} 2")
+        // Sometimes first run observes stale package data
+        // so run twice to prevent that
+        repeat(2) {
+            eventually {
+                runShellCommandOrThrow("cmd jobscheduler run -u 0 " +
+                    "-f ${context.packageManager.permissionControllerPackageName} 2")
+            }
+        }
     }
 
     private inline fun <T> withDeviceConfig(
@@ -324,23 +322,43 @@
     }
 
     private fun installApp(apk: String = APK_PATH) {
-        assertThat(runShellCommand("pm install -r $apk"), containsString("Success"))
+        assertThat(runShellCommandOrThrow("pm install -r $apk"), containsString("Success"))
     }
 
     private fun uninstallApp(packageName: String = APK_PACKAGE_NAME) {
-        assertThat(runShellCommand("pm uninstall $packageName"), containsString("Success"))
+        assertThat(runShellCommandOrThrow("pm uninstall $packageName"), containsString("Success"))
     }
 
     private fun startApp(packageName: String = APK_PACKAGE_NAME) {
+        // Don't throw on stderr - command may print a warning if app already running
         runShellCommand("am start -n $packageName/$packageName.MainActivity")
+        awaitAppState(packageName, lessThanOrEqualTo(IMPORTANCE_TOP_SLEEPING))
     }
 
     private fun goHome() {
-        runShellCommand("input keyevent KEYCODE_HOME")
+        runShellCommandOrThrow("input keyevent KEYCODE_HOME")
     }
 
     private fun goBack() {
-        runShellCommand("input keyevent KEYCODE_BACK")
+        runShellCommandOrThrow("input keyevent KEYCODE_BACK")
+    }
+
+    private fun killDummyApp(pkg: String = APK_PACKAGE_NAME) {
+        assertThat(
+                runShellCommandOrThrow("am force-stop " + pkg),
+                equalTo(""))
+        awaitAppState(pkg, greaterThan(IMPORTANCE_TOP_SLEEPING))
+    }
+
+    private fun awaitAppState(pkg: String, stateMatcher: Matcher<Int>) {
+        eventually {
+            runWithShellPermissionIdentity {
+                val packageImportance = context
+                        .getSystemService(ActivityManager::class.java)!!
+                        .getPackageImportance(pkg)
+                assertThat(packageImportance, stateMatcher)
+            }
+        }
     }
 
     private fun clickPermissionAllow() {
@@ -392,6 +410,7 @@
 
     private fun click(label: String) {
         waitFindNode(hasTextThat(containsStringIgnoringCase(label))).click()
+        waitForIdle()
     }
 
     private fun assertWhitelistState(state: Boolean) {
diff --git a/tests/tests/os/src/android/os/cts/BundleTest.java b/tests/tests/os/src/android/os/cts/BundleTest.java
index ca70fe3..9eb9ddf 100644
--- a/tests/tests/os/src/android/os/cts/BundleTest.java
+++ b/tests/tests/os/src/android/os/cts/BundleTest.java
@@ -847,10 +847,10 @@
     }
 
     private void assertSpannableEquals(Spannable expected, CharSequence observed) {
-        Spannable s = (Spannable) observed;
+        final Spannable observedSpan = (Spannable) observed;
         assertEquals(expected.toString(), observed.toString());
         Object[] expectedSpans = expected.getSpans(0, expected.length(), Object.class);
-        Object[] observedSpans = expected.getSpans(0, expected.length(), Object.class);
+        Object[] observedSpans = observedSpan.getSpans(0, observedSpan.length(), Object.class);
         assertEquals(expectedSpans.length, observedSpans.length);
         for (int i = 0; i < expectedSpans.length; i++) {
             // Can't compare values of arbitrary objects
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
index 773551b..84fad25 100644
--- a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -26,6 +26,8 @@
 import com.android.compatibility.common.util.SystemUtil.runShellCommand
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.compatibility.common.util.ThrowingSupplier
+import org.hamcrest.CoreMatchers.containsString
+import org.junit.Assert.assertThat
 import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
@@ -99,4 +101,22 @@
             COMPANION_APPROVE_WIFI_CONNECTIONS))
         assertFalse(isShellAssociated(DUMMY_MAC_ADDRESS, SHELL_PACKAGE_NAME))
     }
+
+    @AppModeFull(reason = "Companion API for non-instant apps only")
+    @Test
+    fun testDump() {
+        val userId = context.userId
+        val packageName = context.packageName
+
+        try {
+            runShellCommand(
+                    "cmd companiondevice associate $userId $packageName $DUMMY_MAC_ADDRESS")
+            val output = runShellCommand("dumpsys companiondevice")
+            assertThat(output, containsString(packageName))
+            assertThat(output, containsString(DUMMY_MAC_ADDRESS))
+        } finally {
+            runShellCommand(
+                    "cmd companiondevice disassociate $userId $packageName $DUMMY_MAC_ADDRESS")
+        }
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/PowerManagerTest.java b/tests/tests/os/src/android/os/cts/PowerManagerTest.java
index 0d9b71f..70bf9c1 100644
--- a/tests/tests/os/src/android/os/cts/PowerManagerTest.java
+++ b/tests/tests/os/src/android/os/cts/PowerManagerTest.java
@@ -18,22 +18,32 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings.Global;
 import android.test.AndroidTestCase;
+
 import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.CallbackAsserter;
 import com.android.compatibility.common.util.SystemUtil;
+
 import org.junit.After;
 import org.junit.Before;
 
+import java.time.Duration;
+
 @AppModeFull(reason = "Instant Apps don't have the WRITE_SECURE_SETTINGS permission "
         + "required in tearDown for Global#putInt")
 public class PowerManagerTest extends AndroidTestCase {
     private static final String TAG = "PowerManagerTest";
     public static final long TIME = 3000;
     public static final int MORE_TIME = 300;
+    private static final int BROADCAST_TIMEOUT_SECONDS = 60;
+    private static final Duration LONG_DISCHARGE_DURATION = Duration.ofMillis(2000);
+    private static final Duration SHORT_DISCHARGE_DURATION = Duration.ofMillis(1000);
 
     private int mInitialPowerSaverMode;
     private int mInitialDynamicPowerSavingsEnabled;
@@ -124,4 +134,63 @@
                     Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 0));
         });
     }
+
+    public void testPowerManager_batteryDischargePrediction() throws Exception {
+        final PowerManager manager = BatteryUtils.getPowerManager();
+
+        if (!BatteryUtils.hasBattery()) {
+            assertNull(manager.getBatteryDischargePrediction());
+            return;
+        }
+
+        // Unplug to ensure the plugged in broadcast is sent.
+        BatteryUtils.runDumpsysBatteryUnplug();
+
+        // Plugged in. No prediction should be given.
+        final CallbackAsserter pluggedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(Intent.ACTION_POWER_CONNECTED));
+        BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        pluggedBroadcastAsserter.assertCalled("Didn't get power connected broadcast",
+                BROADCAST_TIMEOUT_SECONDS);
+        assertNull(manager.getBatteryDischargePrediction());
+
+        // Not plugged in. At the very least, the basic discharge estimation should be returned.
+        final CallbackAsserter unpluggedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(Intent.ACTION_POWER_DISCONNECTED));
+        BatteryUtils.runDumpsysBatteryUnplug();
+        unpluggedBroadcastAsserter.assertCalled("Didn't get power disconnected broadcast",
+                BROADCAST_TIMEOUT_SECONDS);
+        assertNotNull(manager.getBatteryDischargePrediction());
+
+        CallbackAsserter predictionChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(PowerManager.ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED));
+        setDischargePrediction(LONG_DISCHARGE_DURATION, true);
+        assertDischargePrediction(LONG_DISCHARGE_DURATION, true);
+        predictionChangedBroadcastAsserter.assertCalled("Prediction changed broadcast not received",
+                BROADCAST_TIMEOUT_SECONDS);
+
+
+        predictionChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(PowerManager.ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED));
+        setDischargePrediction(SHORT_DISCHARGE_DURATION, false);
+        assertDischargePrediction(SHORT_DISCHARGE_DURATION, false);
+        predictionChangedBroadcastAsserter.assertCalled("Prediction changed broadcast not received",
+                BROADCAST_TIMEOUT_SECONDS);
+    }
+
+    private void setDischargePrediction(Duration d, boolean isPersonalized) {
+        final PowerManager manager = BatteryUtils.getPowerManager();
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> manager.setBatteryDischargePrediction(d, isPersonalized));
+    }
+
+    private void assertDischargePrediction(Duration d, boolean isPersonalized) {
+        final PowerManager manager = BatteryUtils.getPowerManager();
+        // We can't pause time so must use >= because the time remaining should decrease as
+        // time goes on.
+        Duration prediction = manager.getBatteryDischargePrediction();
+        assertTrue("Prediction is greater than " + d.toMillis() + "ms: "
+                + prediction, d.toMillis() >= prediction.toMillis());
+        assertEquals(isPersonalized, manager.isBatteryDischargePredictionPersonalized());
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/SimpleTestActivity.java b/tests/tests/os/src/android/os/cts/SimpleTestActivity.java
index 22c706f..3acbce6 100644
--- a/tests/tests/os/src/android/os/cts/SimpleTestActivity.java
+++ b/tests/tests/os/src/android/os/cts/SimpleTestActivity.java
@@ -16,30 +16,6 @@
 
 package android.os.cts;
 
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
-
 import android.app.Activity;
-import android.os.Bundle;
-import android.widget.EditText;
-import android.widget.LinearLayout;
 
-public class SimpleTestActivity extends Activity {
-    private EditText mEditText;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        mEditText = new EditText(this);
-        final LinearLayout layout = new LinearLayout(this);
-        layout.setOrientation(LinearLayout.VERTICAL);
-        layout.addView(mEditText);
-        setContentView(layout);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
-        mEditText.requestFocus();
-    }
-}
\ No newline at end of file
+public class SimpleTestActivity extends Activity {}
\ No newline at end of file
diff --git a/tests/tests/os/src/android/os/cts/StrictModeTest.java b/tests/tests/os/src/android/os/cts/StrictModeTest.java
index 562fc5e..5f6d570 100644
--- a/tests/tests/os/src/android/os/cts/StrictModeTest.java
+++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java
@@ -17,15 +17,9 @@
 package android.os.cts;
 
 import static android.content.Context.WINDOW_SERVICE;
-import static android.content.pm.PackageManager.FEATURE_INPUT_METHODS;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static com.android.cts.mockime.ImeEventStreamTestUtils.clearAllEvents;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
-
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -33,6 +27,7 @@
 import static org.junit.Assert.fail;
 
 import android.app.Activity;
+import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -40,7 +35,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
-import android.inputmethodservice.InputMethodService;
 import android.net.TrafficStats;
 import android.net.Uri;
 import android.os.IBinder;
@@ -66,19 +60,14 @@
 import android.system.OsConstants;
 import android.util.Log;
 import android.view.Display;
+import android.view.GestureDetector;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
-import androidx.annotation.IntDef;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.cts.mockime.ImeEvent;
-import com.android.cts.mockime.ImeEventStream;
-import com.android.cts.mockime.ImeSettings;
-import com.android.cts.mockime.MockImeSession;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -90,8 +79,6 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.net.HttpURLConnection;
 import java.net.Socket;
 import java.net.URL;
@@ -110,51 +97,14 @@
 public class StrictModeTest {
     private static final String TAG = "StrictModeTest";
     private static final String REMOTE_SERVICE_ACTION = "android.app.REMOTESERVICE";
-    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10); // 10 seconds
-    private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
 
     private StrictMode.ThreadPolicy mThreadPolicy;
     private StrictMode.VmPolicy mVmPolicy;
 
-    // TODO(b/160143006): re-enable IMS part test.
-    private static final boolean DISABLE_VERIFY_IMS = false;
-
-    /**
-     * Verify mode to verifying if APIs violates incorrect context violation.
-     *
-     * @see #VERIFY_MODE_GET_DISPLAY
-     * @see #VERIFY_MODE_GET_WINDOW_MANAGER
-     * @see #VERIFY_MODE_GET_VIEW_CONFIGURATION
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, value = {
-            VERIFY_MODE_GET_DISPLAY,
-            VERIFY_MODE_GET_WINDOW_MANAGER,
-            VERIFY_MODE_GET_VIEW_CONFIGURATION,
-    })
-    private @interface VerifyMode {}
-
-    /**
-     * Verifies if {@link Context#getDisplay} from {@link InputMethodService} and context created
-     * from {@link InputMethodService#createConfigurationContext(Configuration)} violates
-     * incorrect context violation.
-     */
-    private static final int VERIFY_MODE_GET_DISPLAY = 1;
-    /**
-     * Verifies if get {@link android.view.WindowManager} from {@link InputMethodService} and
-     * context created from {@link InputMethodService#createConfigurationContext(Configuration)}
-     * violates incorrect context violation.
-     *
-     * @see Context#getSystemService(String)
-     * @see Context#getSystemService(Class)
-     */
-    private static final int VERIFY_MODE_GET_WINDOW_MANAGER = 2;
-    /**
-     * Verifies if passing {@link InputMethodService} and context created
-     * from {@link InputMethodService#createConfigurationContext(Configuration)} to
-     * {@link android.view.ViewConfiguration#get(Context)} violates incorrect context violation.
-     */
-    private static final int VERIFY_MODE_GET_VIEW_CONFIGURATION = 3;
+    private Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    private GestureDetector.OnGestureListener mGestureListener =
+            new GestureDetector.SimpleOnGestureListener();
+    private static final String WM_CLASS_NAME = WindowManager.class.getSimpleName();
 
     private Context getContext() {
         return ApplicationProvider.getApplicationContext();
@@ -683,150 +633,186 @@
     }
 
     @Test
-    public void testIncorrectContextUse_GetSystemService() throws Exception {
+    public void testIncorrectContextUse_Application_ThrowViolation() throws Exception {
         StrictMode.setVmPolicy(
                 new StrictMode.VmPolicy.Builder()
                         .detectIncorrectContextUse()
                         .penaltyLog()
                         .build());
 
-        final String wmClassName = WindowManager.class.getSimpleName();
-        inspectViolation(
-                () -> getContext().getApplicationContext().getSystemService(WindowManager.class),
-                info -> assertThat(info.getStackTrace()).contains(
-                        "Tried to access visual service " + wmClassName));
+        final Context applicationContext = getContext();
 
-        final Display display = getContext().getSystemService(DisplayManager.class)
-                .getDisplay(DEFAULT_DISPLAY);
-        final Context visualContext = getContext().createDisplayContext(display)
-                .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
-        assertNoViolation(() -> visualContext.getSystemService(WINDOW_SERVICE));
+        assertViolation("Tried to access visual service " + WM_CLASS_NAME,
+                () -> applicationContext.getSystemService(WindowManager.class));
 
-        Intent intent = new Intent(getContext(), SimpleTestActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        final Activity activity = InstrumentationRegistry.getInstrumentation()
-                .startActivitySync(intent);
-        assertNoViolation(() -> activity.getSystemService(WINDOW_SERVICE));
-
-        // TODO(b/159593676): move the logic to CtsInputMethodTestCases
-        verifyIms(VERIFY_MODE_GET_WINDOW_MANAGER);
-    }
-
-    @Test
-    public void testIncorrectContextUse_GetDisplay() throws Exception {
-        StrictMode.setVmPolicy(
-                new StrictMode.VmPolicy.Builder()
-                        .detectIncorrectContextUse()
-                        .penaltyLog()
-                        .build());
-
-        final Display display = getContext().getSystemService(DisplayManager.class)
-                .getDisplay(DEFAULT_DISPLAY);
-
-        final Context displayContext = getContext().createDisplayContext(display);
-        assertNoViolation(displayContext::getDisplay);
-
-        final Context windowContext =
-                displayContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
-        assertNoViolation(windowContext::getDisplay);
-
-        Intent intent = new Intent(getContext(), SimpleTestActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        final Activity activity = InstrumentationRegistry.getInstrumentation()
-                .startActivitySync(intent);
-        assertNoViolation(() -> activity.getDisplay());
-
-        // TODO(b/159593676): move the logic to CtsInputMethodTestCases
-        verifyIms(VERIFY_MODE_GET_DISPLAY);
-        try {
-            getContext().getApplicationContext().getDisplay();
-        } catch (UnsupportedOperationException e) {
-            return;
-        }
-        fail("Expected to get incorrect use exception from calling getDisplay() on Application");
-    }
-
-    @Test
-    public void testIncorrectContextUse_GetViewConfiguration() throws Exception {
-        StrictMode.setVmPolicy(
-                new StrictMode.VmPolicy.Builder()
-                        .detectIncorrectContextUse()
-                        .penaltyLog()
-                        .build());
-
-        final Context baseContext = getContext();
         assertViolation(
                 "Tried to access UI constants from a non-visual Context:",
-                () -> ViewConfiguration.get(baseContext));
+                () -> ViewConfiguration.get(applicationContext));
 
-        final Display display = baseContext.getSystemService(DisplayManager.class)
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertViolation("Tried to access UI constants from a non-visual Context.",
+                        () -> new GestureDetector(applicationContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+    }
+
+    @Test
+    public void testIncorrectContextUse_DisplayContext_ThrowViolation() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectIncorrectContextUse()
+                        .penaltyLog()
+                        .build());
+
+        final Display display = getContext().getSystemService(DisplayManager.class)
                 .getDisplay(DEFAULT_DISPLAY);
-        final Context displayContext = baseContext.createDisplayContext(display);
+        final Context displayContext = getContext().createDisplayContext(display);
+
+        assertViolation("Tried to access visual service " + WM_CLASS_NAME,
+                () -> displayContext.getSystemService(WindowManager.class));
+
         assertViolation(
                 "Tried to access UI constants from a non-visual Context:",
                 () -> ViewConfiguration.get(displayContext));
 
-        final Context windowContext =
-                displayContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertViolation("Tried to access UI constants from a non-visual Context.",
+                        () -> new GestureDetector(displayContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+    }
+
+    @Test
+    public void testIncorrectContextUse_WindowContext_NoViolation() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectIncorrectContextUse()
+                        .penaltyLog()
+                        .build());
+
+        final Context windowContext = createWindowContext();
+
+        assertNoViolation(() -> windowContext.getSystemService(WINDOW_SERVICE));
+
         assertNoViolation(() -> ViewConfiguration.get(windowContext));
 
-        Intent intent = new Intent(baseContext, SimpleTestActivity.class);
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertNoViolation(() -> new GestureDetector(windowContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+    }
+
+    @Test
+    public void testIncorrectContextUse_Activity_NoViolation() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectIncorrectContextUse()
+                        .penaltyLog()
+                        .build());
+
+        Intent intent = new Intent(getContext(), SimpleTestActivity.class);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        final Activity activity = InstrumentationRegistry.getInstrumentation()
-                .startActivitySync(intent);
+        final Activity activity = mInstrumentation.startActivitySync(intent);
+
+        assertNoViolation(() -> activity.getSystemService(WINDOW_SERVICE));
+
         assertNoViolation(() -> ViewConfiguration.get(activity));
 
-        // TODO(b/159593676): move the logic to CtsInputMethodTestCases
-        verifyIms(VERIFY_MODE_GET_VIEW_CONFIGURATION);
-    }
-
-    // TODO(b/159593676): move the logic to CtsInputMethodTestCases
-    /**
-     * Verify if APIs violates incorrect context violations by {@code mode}.
-     *
-     * @see VerifyMode
-     */
-    private void verifyIms(@VerifyMode int mode) throws Exception {
-        // If devices do not support installable IMEs, finish the test gracefully. We don't use
-        // assumeTrue here because we do pass some cases, so showing "pass" instead of "skip" makes
-        // sense here.
-        // TODO(b/160143006): re-enable IMS part test.
-        if (!supportsInstallableIme() || DISABLE_VERIFY_IMS) {
-            return;
-        }
-
-        try (final MockImeSession imeSession = MockImeSession.create(getContext(),
-                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                new ImeSettings.Builder().setStrictModeEnabled(true))) {
-            final ImeEventStream stream = imeSession.openEventStream();
-            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
-            final ImeEventStream forkedStream = clearAllEvents(stream, "onStrictModeViolated");
-            final ImeEvent imeEvent;
-            switch (mode) {
-                case VERIFY_MODE_GET_DISPLAY:
-                    imeEvent = expectCommand(forkedStream, imeSession.callVerifyGetDisplay(),
-                            TIMEOUT);
-                    break;
-                case VERIFY_MODE_GET_WINDOW_MANAGER:
-                    imeEvent = expectCommand(forkedStream, imeSession.callVerifyGetWindowManager(),
-                            TIMEOUT);
-                    break;
-                case VERIFY_MODE_GET_VIEW_CONFIGURATION:
-                    imeEvent = expectCommand(forkedStream,
-                            imeSession.callVerifyGetViewConfiguration(), TIMEOUT);
-                    break;
-                default:
-                    imeEvent = null;
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertNoViolation(() -> new GestureDetector(activity, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
             }
-            assertTrue(imeEvent.getReturnBooleanValue());
-            notExpectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
-                    NOT_EXPECT_TIMEOUT);
-        }
+        });
     }
 
-    private boolean supportsInstallableIme() {
-        return getContext().getPackageManager().hasSystemFeature(FEATURE_INPUT_METHODS);
+    @Test
+    public void testIncorrectContextUse_UiDerivedContext_NoViolation() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectIncorrectContextUse()
+                        .penaltyLog()
+                        .build());
+
+        final Configuration config = new Configuration();
+        config.setToDefaults();
+        final Context uiDerivedConfigContext =
+                createWindowContext().createConfigurationContext(config);
+
+        assertNoViolation(() -> uiDerivedConfigContext.getSystemService(WINDOW_SERVICE));
+
+        assertNoViolation(() -> ViewConfiguration.get(uiDerivedConfigContext));
+
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertNoViolation(() ->
+                        new GestureDetector(uiDerivedConfigContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+
+        final Context uiDerivedAttrContext = createWindowContext()
+                .createAttributionContext(null /* attributeTag */);
+
+        assertNoViolation(() -> uiDerivedAttrContext.getSystemService(WINDOW_SERVICE));
+
+        assertNoViolation(() -> ViewConfiguration.get(uiDerivedAttrContext));
+
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertNoViolation(() ->
+                        new GestureDetector(uiDerivedAttrContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+    }
+
+    @Test
+    public void testIncorrectContextUse_UiDerivedDisplayContext_ThrowViolation() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectIncorrectContextUse()
+                        .penaltyLog()
+                        .build());
+
+        final Display display = getContext().getSystemService(DisplayManager.class)
+                .getDisplay(DEFAULT_DISPLAY);
+        final Context uiDerivedDisplayContext = createWindowContext().createDisplayContext(display);
+
+        assertViolation("Tried to access visual service " + WM_CLASS_NAME,
+                () -> uiDerivedDisplayContext.getSystemService(WindowManager.class));
+
+        assertViolation(
+                "Tried to access UI constants from a non-visual Context:",
+                () -> ViewConfiguration.get(uiDerivedDisplayContext));
+
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertViolation("Tried to access UI constants from a non-visual Context.",
+                        () -> new GestureDetector(uiDerivedDisplayContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+    }
+
+    private Context createWindowContext() {
+        final Display display = getContext().getSystemService(DisplayManager.class)
+                .getDisplay(DEFAULT_DISPLAY);
+        return getContext().createDisplayContext(display)
+                .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
     }
 
     private static void runWithRemoteServiceBound(Context context, Consumer<ISecondary> consumer)
diff --git a/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java b/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
index f6fb8f3..966780b 100644
--- a/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
@@ -32,9 +32,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class VibrationAttributesTest {
-    private static final int TEST_USAGE_UNKNOWN = AudioAttributes.USAGE_UNKNOWN;
     private static final int TEST_USAGE = AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
-    private static final int TEST_USAGE2 = AudioAttributes.USAGE_NOTIFICATION;
 
     private static final int TEST_AMPLITUDE = 100;
     private static final long TEST_TIMING_LONG = 5000;
@@ -56,34 +54,68 @@
 
     @Test
     public void testCreate() {
-        AudioAttributes tmp = new AudioAttributes.Builder().setUsage(TEST_USAGE).build();
+        AudioAttributes tmp = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ALARM)
+                .build();
         VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
-        assertEquals(attr.getUsage(), VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+        assertEquals(attr.getUsage(), VibrationAttributes.USAGE_ALARM);
         assertEquals(attr.getUsageClass(), VibrationAttributes.USAGE_CLASS_ALARM);
         assertEquals(attr.getFlags(), 0);
-        assertEquals(attr.getAudioAttributes(), tmp);
+        assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_ALARM);
+    }
+
+    @Test
+    public void testGetAudioUsageReturnOriginalUsage() {
+        AudioAttributes tmp = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
+                .build();
+        VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
+        assertEquals(attr.getUsage(), VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+        assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY);
+    }
+
+    @Test
+    public void testGetAudioUsageUnknownReturnsBasedOnVibrationUsage() {
+        VibrationAttributes attr = new VibrationAttributes.Builder()
+                .setUsage(VibrationAttributes.USAGE_NOTIFICATION).build();
+        assertEquals(attr.getUsage(), VibrationAttributes.USAGE_NOTIFICATION);
+        assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_NOTIFICATION);
     }
 
     @Test
     public void testEquals() {
-        AudioAttributes tmp = new AudioAttributes.Builder().setUsage(TEST_USAGE).build();
+        AudioAttributes tmp = createAudioAttributes(TEST_USAGE);
         VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
         VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp, null).build();
         assertEquals(attr, attr2);
     }
 
     @Test
-    public void testNotEqualsDifferentUsage() {
-        AudioAttributes tmp = new AudioAttributes.Builder().setUsage(TEST_USAGE).build();
+    public void testNotEqualsDifferentAudioUsage() {
+        AudioAttributes tmp = createAudioAttributes(
+                AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT);
         VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
-        AudioAttributes tmp2 = new AudioAttributes.Builder().setUsage(TEST_USAGE2).build();
+        AudioAttributes tmp2 = createAudioAttributes(
+                AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED);
         VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp2, null).build();
+        assertEquals(attr.getUsage(), attr2.getUsage());
+        assertNotEquals(attr, attr2);
+    }
+
+    @Test
+    public void testNotEqualsDifferentVibrationUsage() {
+        VibrationAttributes attr = new VibrationAttributes.Builder()
+                .setUsage(VibrationAttributes.USAGE_TOUCH)
+                .build();
+        VibrationAttributes attr2 = new VibrationAttributes.Builder()
+                .setUsage(VibrationAttributes.USAGE_NOTIFICATION)
+                .build();
         assertNotEquals(attr, attr2);
     }
 
     @Test
     public void testNotEqualsDifferentFlags() {
-        AudioAttributes tmp = new AudioAttributes.Builder().setUsage(TEST_USAGE).build();
+        AudioAttributes tmp = createAudioAttributes(TEST_USAGE);
         VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
         VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp, null).setFlags(1, 1)
                 .build();
@@ -92,7 +124,7 @@
 
     @Test
     public void testHeuristics() {
-        AudioAttributes tmp = new AudioAttributes.Builder().setUsage(TEST_USAGE_UNKNOWN).build();
+        AudioAttributes tmp = createAudioAttributes(AudioAttributes.USAGE_UNKNOWN);
         VibrationAttributes oneShotLong =
             new VibrationAttributes.Builder(tmp, TEST_ONE_SHOT_LONG).build();
         VibrationAttributes oneShotShort =
@@ -104,10 +136,19 @@
         VibrationAttributes prebaked =
             new VibrationAttributes.Builder(tmp, TEST_PREBAKED).build();
         assertEquals(oneShotShort.getUsage(), VibrationAttributes.USAGE_TOUCH);
+        assertEquals(oneShotShort.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
         assertEquals(waveformShort.getUsage(), VibrationAttributes.USAGE_TOUCH);
+        assertEquals(waveformShort.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
         assertEquals(oneShotLong.getUsage(), VibrationAttributes.USAGE_UNKNOWN);
+        assertEquals(oneShotLong.getAudioUsage(), AudioAttributes.USAGE_UNKNOWN);
         assertEquals(waveformLong.getUsage(), VibrationAttributes.USAGE_UNKNOWN);
+        assertEquals(waveformLong.getAudioUsage(), AudioAttributes.USAGE_UNKNOWN);
         assertEquals(prebaked.getUsage(), VibrationAttributes.USAGE_TOUCH);
+        assertEquals(prebaked.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+    }
+
+    private static AudioAttributes createAudioAttributes(int usage) {
+        return new AudioAttributes.Builder().setUsage(usage).build();
     }
 }
 
diff --git a/tests/tests/os/src/android/os/cts/VibratorTest.java b/tests/tests/os/src/android/os/cts/VibratorTest.java
index 278bea3..459aaa2 100644
--- a/tests/tests/os/src/android/os/cts/VibratorTest.java
+++ b/tests/tests/os/src/android/os/cts/VibratorTest.java
@@ -19,6 +19,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
@@ -36,6 +38,8 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import java.util.concurrent.Executors;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -234,8 +238,8 @@
     public void testVibratorStateCallback() throws Exception {
         final UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         ui.adoptShellPermissionIdentity("android.permission.ACCESS_VIBRATOR_STATE");
-        // Add listener1
-        mVibrator.addVibratorStateListener(mListener1);
+        // Add listener1 on executor
+        mVibrator.addVibratorStateListener(Executors.newSingleThreadExecutor(), mListener1);
         // Add listener2 on main thread.
         mVibrator.addVibratorStateListener(mListener2);
         verify(mListener1, timeout(CALLBACK_TIMEOUT_MILLIS)
@@ -251,14 +255,26 @@
         verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLIS)
                 .times(1)).onVibratorStateChanged(true);
 
-        reset(mListener1);
-        reset(mListener2);
-        // Remove listener1
-        mVibrator.removeVibratorStateListener(mListener1);
-        mVibrator.removeVibratorStateListener(mListener2);
+        verify(mListener1, timeout(CALLBACK_TIMEOUT_MILLIS)
+                .times(1)).onVibratorStateChanged(false);
+        verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLIS)
+                .times(1)).onVibratorStateChanged(false);
 
         mVibrator.cancel();
         assertIsVibrating(false);
+
+        reset(mListener1);
+        reset(mListener2);
+        // Remove listener1 & listener2
+        mVibrator.removeVibratorStateListener(mListener1);
+        mVibrator.removeVibratorStateListener(mListener2);
+
+        mVibrator.vibrate(1000);
+        assertIsVibrating(true);
+        verify(mListener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(0))
+                .onVibratorStateChanged(anyBoolean());
+        verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(0))
+                .onVibratorStateChanged(anyBoolean());
     }
 
     private static void sleep(long millis) {
diff --git a/tests/tests/os/src/android/os/storage/cts/StorageCrateTest.java b/tests/tests/os/src/android/os/storage/cts/StorageCrateTest.java
index 99ea30c..c1172ff 100644
--- a/tests/tests/os/src/android/os/storage/cts/StorageCrateTest.java
+++ b/tests/tests/os/src/android/os/storage/cts/StorageCrateTest.java
@@ -314,7 +314,7 @@
         }
 
         String[] newChildDir = mCratesRoot.toFile().list();
-        assertThat(newChildDir).asList().containsAllIn(expectedCrates);
+        assertThat(newChildDir).asList().containsAtLeastElementsIn(expectedCrates);
     }
 
     @Test
diff --git a/tests/tests/os/src/android/os/storage/cts/StorageStatsManagerTest.java b/tests/tests/os/src/android/os/storage/cts/StorageStatsManagerTest.java
index 60ede8b..226bb7a 100644
--- a/tests/tests/os/src/android/os/storage/cts/StorageStatsManagerTest.java
+++ b/tests/tests/os/src/android/os/storage/cts/StorageStatsManagerTest.java
@@ -380,17 +380,10 @@
         assertThat(newCollection.size()).isEqualTo(oldCollection.size() - 1);
     }
 
-    Correspondence<CrateInfo, String> mCorrespondenceByLabel = new Correspondence<>() {
-        @Override
-        public boolean compare(CrateInfo crateInfo, String expect) {
+    Correspondence<CrateInfo, String> mCorrespondenceByLabel =
+        Correspondence.from((CrateInfo crateInfo, String expect) -> {
             return TextUtils.equals(crateInfo.getLabel(), expect);
-        }
-
-        @Override
-        public String toString() {
-            return "It should be the crated folder name";
-        }
-    };
+        }, "It should be the crated folder name");
 
     @Test
     public void queryCratesForUid_createDeepPath_shouldCreateOneCrate()
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/AndroidManifest.xml b/tests/tests/packageinstaller/adminpackageinstaller/AndroidManifest.xml
index 3867e9f..4cdcdaa 100755
--- a/tests/tests/packageinstaller/adminpackageinstaller/AndroidManifest.xml
+++ b/tests/tests/packageinstaller/adminpackageinstaller/AndroidManifest.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2017 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,35 +15,37 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.packageinstaller.admin.cts" >
+     package="android.packageinstaller.admin.cts">
 
-    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <application android:label="Cts Admin Package Installer Test" android:testOnly="true">
+    <application android:label="Cts Admin Package Installer Test"
+         android:testOnly="true">
         <uses-library android:name="android.test.runner"/>
 
-        <receiver
-            android:name=".BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name=".BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <activity android:name=".LauncherActivity">
+        <activity android:name=".LauncherActivity"
+             android:exported="true">
             <intent-filter android:priority="-999">
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:functionalTest="true"
-                     android:targetPackage="android.packageinstaller.admin.cts"
-                     android:label="External App Sources Tests"/>
+         android:functionalTest="true"
+         android:targetPackage="android.packageinstaller.admin.cts"
+         android:label="External App Sources Tests"/>
 </manifest>
diff --git a/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml b/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
index 6ab9d5a..79ea698 100644
--- a/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
+++ b/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
@@ -27,8 +27,10 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
         <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.C" />
         <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
         <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.C" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java b/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
index 8c1623d..7c53349 100644
--- a/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
+++ b/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
@@ -29,6 +29,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.cts.install.lib.Install;
 import com.android.cts.install.lib.InstallUtils;
 import com.android.cts.install.lib.LocalIntentSender;
@@ -41,11 +42,22 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
 /**
  * Tests for multi-package (a.k.a. atomic) installs.
  */
 @RunWith(JUnit4.class)
 public class AtomicInstallTest {
+    /**
+     * Time between repeated checks in {@link #retry}.
+     */
+    private static final long RETRY_CHECK_INTERVAL_MILLIS = 500;
+    /**
+     * Maximum number of checks in {@link #retry} before a timeout occurs.
+     */
+    private static final long RETRY_MAX_INTERVALS = 20;
 
     public static final String TEST_APP_CORRUPT_FILENAME = "corrupt.apk";
     private static final TestApp CORRUPT_TESTAPP = new TestApp(
@@ -63,7 +75,7 @@
     public void setup() throws Exception {
         adoptShellPermissions();
 
-        Uninstall.packages(TestApp.A, TestApp.B);
+        Uninstall.packages(TestApp.A, TestApp.B, TestApp.C);
     }
 
     @After
@@ -74,6 +86,60 @@
                 .dropShellPermissionIdentity();
     }
 
+    /**
+     * Cleans up sessions that are not committed during tests.
+     */
+    @After
+    public void cleanUpSessions() {
+        InstallUtils.getPackageInstaller().getMySessions().forEach(info -> {
+            try {
+                InstallUtils.getPackageInstaller().abandonSession(info.getSessionId());
+            } catch (Exception ignore) {
+            }
+        });
+    }
+
+    private static <T> T retry(Supplier<T> supplier, Predicate<T> predicate, String message)
+            throws InterruptedException {
+        for (int i = 0; i < RETRY_MAX_INTERVALS; i++) {
+            T result = supplier.get();
+            if (predicate.test(result)) {
+                return result;
+            }
+            Thread.sleep(RETRY_CHECK_INTERVAL_MILLIS);
+        }
+        throw new AssertionError(message);
+    }
+
+    /**
+     * Tests a completed session should be cleaned up.
+     */
+    @Test
+    public void testSessionCleanUp_Single() throws Exception {
+        int sessionId = Install.single(TestApp.A1).commit();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+        // The session is cleaned up asynchronously after install completed.
+        // Retry until the session no longer exists.
+        retry(() -> InstallUtils.getPackageInstaller().getSessionInfo(sessionId),
+                info -> info == null,
+                "Session " + sessionId + " not cleaned up");
+    }
+
+    /**
+     * Tests a completed session should be cleaned up.
+     */
+    @Test
+    public void testSessionCleanUp_Multi() throws Exception {
+        int sessionId = Install.multi(TestApp.A1, TestApp.B1).commit();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
+        // The session is cleaned up asynchronously after install completed.
+        // Retry until the session no longer exists.
+        retry(() -> InstallUtils.getPackageInstaller().getSessionInfo(sessionId),
+                info -> info == null,
+                "Session " + sessionId + " not cleaned up");
+    }
+
     @Test
     public void testInstallTwoApks() throws Exception {
         Install.multi(TestApp.A1, TestApp.B1).commit();
@@ -81,6 +147,31 @@
         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
     }
 
+    /**
+     * Tests a removed child shouldn't be installed.
+     */
+    @Test
+    public void testRemoveChild() throws Exception {
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.C)).isEqualTo(-1);
+
+        int parentId = Install.multi(TestApp.A1).createSession();
+        int childBId = Install.single(TestApp.B1).createSession();
+        int childCId = Install.single(TestApp.C1).createSession();
+        try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
+            parent.addChildSessionId(childBId);
+            parent.addChildSessionId(childCId);
+            parent.removeChildSessionId(childBId);
+            LocalIntentSender sender = new LocalIntentSender();
+            parent.commit(sender.getIntentSender());
+            InstallUtils.assertStatusSuccess(sender.getResult());
+            assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+            assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
+            assertThat(getInstalledVersion(TestApp.C)).isEqualTo(1);
+        }
+    }
+
     @Test
     public void testInstallTwoApksDowngradeFail() throws Exception {
         Install.multi(TestApp.A2, TestApp.B1).commit();
@@ -139,6 +230,117 @@
     }
 
     @Test
+    public void testInvalidStateScenario_MultiSessionCantBeApex() throws Exception {
+        try {
+            SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check true");
+            PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+            params.setMultiPackage();
+            params.setInstallAsApex();
+            params.setStaged();
+            try {
+                InstallUtils.getPackageInstaller().createSession(params);
+                fail("Should not be able to create a multi-session set as APEX!");
+            } catch (Exception ignore) {
+            }
+        } finally {
+            SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check false");
+        }
+    }
+
+    /**
+     * Tests a single-session can't have child.
+     */
+    @Test
+    public void testInvalidStateScenario_AddChildToSingleSessionShouldFail() throws Exception {
+        int parentId = Install.single(TestApp.A1).createSession();
+        int childId = Install.single(TestApp.B1).createSession();
+        try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
+            try {
+                parent.addChildSessionId(childId);
+                fail("Should not be able to add a child session to a single-session!");
+            } catch (Exception ignore) {
+            }
+        }
+    }
+
+    /**
+     * Tests a multi-session can't be a child.
+     */
+    @Test
+    public void testInvalidStateScenario_MultiSessionAddedAsChildShouldFail() throws Exception {
+        int parentId = Install.multi(TestApp.A1).createSession();
+        int childId = Install.multi(TestApp.B1).createSession();
+        try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
+            try {
+                parent.addChildSessionId(childId);
+                fail("Should not be able to add a multi-session as a child!");
+            } catch (Exception ignore) {
+            }
+        }
+    }
+
+    /**
+     * Tests a committed session can't add child.
+     */
+    @Test
+    public void testInvalidStateScenario_AddChildToCommittedSessionShouldFail() throws Exception {
+        int parentId = Install.multi(TestApp.A1).createSession();
+        int childId = Install.single(TestApp.B1).createSession();
+        try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
+            LocalIntentSender sender = new LocalIntentSender();
+            parent.commit(sender.getIntentSender());
+            try {
+                parent.addChildSessionId(childId);
+                fail("Should not be able to add child to a committed session");
+            } catch (Exception ignore) {
+            }
+        }
+    }
+
+    /**
+     * Tests a committed session can't remove child.
+     */
+    @Test
+    public void testInvalidStateScenario_RemoveChildFromCommittedSessionShouldFail()
+            throws Exception {
+        int parentId = Install.multi(TestApp.A1).createSession();
+        int childId = Install.single(TestApp.B1).createSession();
+        try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
+            parent.addChildSessionId(childId);
+            LocalIntentSender sender = new LocalIntentSender();
+            parent.commit(sender.getIntentSender());
+            try {
+                parent.removeChildSessionId(childId);
+                fail("Should not be able to remove child from a committed session");
+            } catch (Exception ignore) {
+            }
+        }
+    }
+
+    /**
+     * Tests removing a child that is not its own should do nothing.
+     */
+    @Test
+    public void testInvalidStateScenario_RemoveWrongChildShouldDoNothing() throws Exception {
+        int parent1Id = Install.multi(TestApp.A1).createSession();
+        int parent2Id = Install.multi(TestApp.C1).createSession();
+        int childId = Install.single(TestApp.B1).createSession();
+        try (PackageInstaller.Session parent1 = openPackageInstallerSession(parent1Id);
+             PackageInstaller.Session parent2 = openPackageInstallerSession(parent2Id);) {
+            parent1.addChildSessionId(childId);
+            // Should do nothing since the child doesn't belong to parent2
+            parent2.removeChildSessionId(childId);
+            int currentParentId =
+                    InstallUtils.getPackageInstaller().getSessionInfo(childId).getParentSessionId();
+            // Check this child still belongs to parent1
+            assertThat(currentParentId).isEqualTo(parent1Id);
+            assertThat(parent1.getChildSessionIds()).asList().contains(childId);
+            assertThat(parent2.getChildSessionIds()).asList().doesNotContain(childId);
+        }
+    }
+
+    @Test
     public void testInvalidStateScenarios() throws Exception {
         int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).createSession();
         try (PackageInstaller.Session parentSession =
@@ -147,7 +349,8 @@
                 try (PackageInstaller.Session childSession =
                              openPackageInstallerSession(childSessionId)) {
                     try {
-                        childSession.commit(LocalIntentSender.getIntentSender());
+                        LocalIntentSender sender = new LocalIntentSender();
+                        childSession.commit(sender.getIntentSender());
                         fail("Should not be able to commit a child session!");
                     } catch (IllegalStateException e) {
                         // ignore
@@ -172,8 +375,9 @@
                 }
             }
 
-            parentSession.commit(LocalIntentSender.getIntentSender());
-            assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+            LocalIntentSender sender = new LocalIntentSender();
+            parentSession.commit(sender.getIntentSender());
+            assertStatusSuccess(sender.getResult());
         }
     }
 
@@ -186,6 +390,6 @@
     }
 
     private static void assertInconsistentSettings(String failMessage, Install install) {
-        InstallUtils.commitExpectingFailure(AssertionError.class, failMessage, install);
+        InstallUtils.commitExpectingFailure(IllegalStateException.class, failMessage, install);
     }
 }
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
index 45eb038..32a47c5 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
@@ -61,6 +61,8 @@
 const val TIMEOUT = 60000L
 const val APP_OP_STR = "REQUEST_INSTALL_PACKAGES"
 
+const val INSTALL_INSTANT_APP = 0x00000800
+
 open class PackageInstallerTestBase {
     @get:Rule
     val installDialogStarter = ActivityTestRule(FutureResultActivity::class.java)
@@ -130,10 +132,20 @@
      * Start an installation via a session
      */
     protected fun startInstallationViaSession(): CompletableFuture<Int> {
+        return startInstallationViaSession(0 /* installFlags */)
+    }
+
+    protected fun startInstallationViaSession(installFlags: Int): CompletableFuture<Int> {
         val pi = pm.packageInstaller
 
         // Create session
-        val sessionId = pi.createSession(PackageInstaller.SessionParams(MODE_FULL_INSTALL))
+        val sessionParam = PackageInstaller.SessionParams(MODE_FULL_INSTALL)
+        // Handle additional install flags
+        if (installFlags and INSTALL_INSTANT_APP != 0) {
+            sessionParam.setInstallAsInstantApp(true)
+        }
+
+        val sessionId = pi.createSession(sessionParam)
         val session = pi.openSession(sessionId)!!
 
         // Write data to session
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
index 69096f8..8df0c6b 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
@@ -114,4 +114,22 @@
             setSecureFrp(false)
         }
     }
+
+    /**
+     * Check that can't install Instant App when installer don't have proper permission.
+     */
+    @Test
+    fun confirmInstantInstallationFails() {
+        try {
+            val installation = startInstallationViaSession(INSTALL_INSTANT_APP)
+            clickInstallerUIButton(CANCEL_BUTTON_ID)
+
+            fail("Expected security exception on instant install from non-system app")
+        } catch (expected: SecurityException) {
+            // Expected
+        }
+
+        // Install should never have started
+        assertNotInstalled()
+    }
 }
diff --git a/tests/tests/packagewatchdog/TEST_MAPPING b/tests/tests/packagewatchdog/TEST_MAPPING
new file mode 100644
index 0000000..17e84ff
--- /dev/null
+++ b/tests/tests/packagewatchdog/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPackageWatchdogTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/permission/AndroidManifest.xml b/tests/tests/permission/AndroidManifest.xml
index 4169ca2..e559076 100644
--- a/tests/tests/permission/AndroidManifest.xml
+++ b/tests/tests/permission/AndroidManifest.xml
@@ -16,42 +16,44 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.permission.cts" android:targetSandboxVersion="2">
+     package="android.permission.cts"
+     android:targetSandboxVersion="2">
 
     <!-- for android.permission.cts.PermissionGroupChange -->
     <permission android:name="android.permission.cts.B"
-                android:protectionLevel="dangerous"
-                android:label="@string/perm_b"
-                android:permissionGroup="android.permission.cts.groupB"
-                android:description="@string/perm_b" />
+         android:protectionLevel="dangerous"
+         android:label="@string/perm_b"
+         android:permissionGroup="android.permission.cts.groupB"
+         android:description="@string/perm_b"/>
 
     <!-- for android.permission.cts.PermissionGroupChange -->
     <permission android:name="android.permission.cts.C"
-                android:protectionLevel="dangerous"
-                android:label="@string/perm_c"
-                android:permissionGroup="android.permission.cts.groupC"
-                android:description="@string/perm_c" />
+         android:protectionLevel="dangerous"
+         android:label="@string/perm_c"
+         android:permissionGroup="android.permission.cts.groupC"
+         android:description="@string/perm_c"/>
 
     <!-- for android.permission.cts.LocationAccessCheckTest -->
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
 
     <!-- for android.permission.cts.PermissionGroupChange -->
     <permission-group android:description="@string/perm_group_b"
-                      android:label="@string/perm_group_b"
-                      android:name="android.permission.cts.groupB" />
+         android:label="@string/perm_group_b"
+         android:name="android.permission.cts.groupB"/>
 
     <!-- for android.permission.cts.PermissionGroupChange -->
     <permission-group android:description="@string/perm_group_c"
-                      android:label="@string/perm_group_c"
-                      android:name="android.permission.cts.groupC" />
+         android:label="@string/perm_group_c"
+         android:name="android.permission.cts.groupC"/>
 
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.permission.cts.PermissionStubActivity"
-                  android:label="PermissionStubActivity">
+             android:label="PermissionStubActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
@@ -59,30 +61,29 @@
         </activity>
 
         <service android:name=".NotificationListener"
-                 android:exported="true"
-                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+             android:exported="true"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
     </application>
 
     <!--
-        The CTS stubs package cannot be used as the target application here,
-        since that requires many permissions to be set. Instead, specify this
-        package itself as the target and include any stub activities needed.
+                The CTS stubs package cannot be used as the target application here,
+                since that requires many permissions to be set. Instead, specify this
+                package itself as the target and include any stub activities needed.
 
-        This test package uses the default InstrumentationTestRunner, because
-        the InstrumentationCtsTestRunner is only available in the stubs
-        package. That runner cannot be added to this package either, since it
-        relies on hidden APIs.
-    -->
+                This test package uses the default InstrumentationTestRunner, because
+                the InstrumentationCtsTestRunner is only available in the stubs
+                package. That runner cannot be added to this package either, since it
+                relies on hidden APIs.
+            -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.permission.cts"
-                     android:label="CTS tests of android.permission">
+         android:targetPackage="android.permission.cts"
+         android:label="CTS tests of android.permission">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index ef2a09f..a558746 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -47,6 +47,7 @@
         <option name="push" value="CtsAppThatRequestsLocationPermission22.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission22.apk" />
         <option name="push" value="CtsAppThatRequestsStoragePermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsStoragePermission29.apk" />
         <option name="push" value="CtsAppThatRequestsStoragePermission28.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsStoragePermission28.apk" />
+        <option name="push" value="CtsAppThatRequestsLocationAndBackgroundPermission28.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationAndBackgroundPermission28.apk" />
         <option name="push" value="CtsAppThatRequestsLocationAndBackgroundPermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationAndBackgroundPermission29.apk" />
         <option name="push" value="CtsAppThatAccessesLocationOnCommand.apk->/data/local/tmp/cts/permissions/CtsAppThatAccessesLocationOnCommand.apk" />
         <option name="push" value="AppThatDoesNotHaveBgLocationAccess.apk->/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk" />
diff --git a/tests/tests/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml b/tests/tests/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml
index e735330..5162a7c 100644
--- a/tests/tests/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml
@@ -16,18 +16,18 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.permission.cts.appthataccesseslocation"
-    android:versionCode="1">
+     package="android.permission.cts.appthataccesseslocation"
+     android:versionCode="1">
 
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
 
     <application android:label="CtsLocationAccess">
-        <service android:name=".AccessLocationOnCommand">
+        <service android:name=".AccessLocationOnCommand"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.permission.cts.accesslocation" />
+                <action android:name="android.permission.cts.accesslocation"/>
             </intent-filter>
         </service>
     </application>
 </manifest>
-
diff --git a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp
new file mode 100644
index 0000000..56bcf8c
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp
@@ -0,0 +1,27 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatRequestsLocationAndBackgroundPermission28",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/AndroidManifest.xml b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/AndroidManifest.xml
new file mode 100644
index 0000000..626ee3d
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.appthatrequestpermission"
+    android:versionCode="3">
+
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <!-- The ACCESS_BACKGROUND_LOCATION was added for API 29. But apps targeting lower APK levels
+    can still request it to signal that they are aware of this new behavior -->
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml b/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml
index cede468..b8e0144 100644
--- a/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml
+++ b/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml
@@ -16,17 +16,17 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.permission.cts.appthatrunsrationaletests">
+     package="android.permission.cts.appthatrunsrationaletests">
 
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
 
     <application android:label="CtsRationaleTests">
-        <activity android:name=".TestActivity">
+        <activity android:name=".TestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="CtsRationalTests.intent.action.Launch" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="CtsRationalTests.intent.action.Launch"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
 </manifest>
-
diff --git a/tests/tests/permission/sdk28/AndroidManifest.xml b/tests/tests/permission/sdk28/AndroidManifest.xml
index 7390f36..1dfeb2a 100644
--- a/tests/tests/permission/sdk28/AndroidManifest.xml
+++ b/tests/tests/permission/sdk28/AndroidManifest.xml
@@ -16,16 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.permission.cts.sdk28">
+     package="android.permission.cts.sdk28">
 
     <uses-sdk android:minSdkVersion="3"
-        android:targetSdkVersion="28"
-        android:maxSdkVersion="28" />
+         android:targetSdkVersion="28"
+         android:maxSdkVersion="28"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.permission.cts.PermissionStubActivity"
-                  android:label="PermissionStubActivity">
+             android:label="PermissionStubActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
@@ -34,21 +35,20 @@
     </application>
 
     <!--
-        The CTS stubs package cannot be used as the target application here,
-        since that requires many permissions to be set. Instead, specify this
-        package itself as the target and include any stub activities needed.
+                The CTS stubs package cannot be used as the target application here,
+                since that requires many permissions to be set. Instead, specify this
+                package itself as the target and include any stub activities needed.
 
-        This test package uses the default InstrumentationTestRunner, because
-        the InstrumentationCtsTestRunner is only available in the stubs
-        package. That runner cannot be added to this package either, since it
-        relies on hidden APIs.
-    -->
+                This test package uses the default InstrumentationTestRunner, because
+                the InstrumentationCtsTestRunner is only available in the stubs
+                package. That runner cannot be added to this package either, since it
+                relies on hidden APIs.
+            -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.permission.cts.sdk28"
-                     android:label="CTS tests of legacy android permissions as of API 28">
+         android:targetPackage="android.permission.cts.sdk28"
+         android:label="CTS tests of legacy android permissions as of API 28">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/permission/sdk28/TEST_MAPPING b/tests/tests/permission/sdk28/TEST_MAPPING
new file mode 100644
index 0000000..b98bbaf
--- /dev/null
+++ b/tests/tests/permission/sdk28/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPermissionTestCasesSdk28"
+    }
+  ]
+}
diff --git a/tests/tests/permission/src/android/permission/cts/BackgroundPermissionsTest.java b/tests/tests/permission/src/android/permission/cts/BackgroundPermissionsTest.java
index 6077b21..a535bb0 100644
--- a/tests/tests/permission/src/android/permission/cts/BackgroundPermissionsTest.java
+++ b/tests/tests/permission/src/android/permission/cts/BackgroundPermissionsTest.java
@@ -30,7 +30,7 @@
 
 import static com.android.compatibility.common.util.SystemUtil.eventually;
 
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
@@ -126,8 +126,8 @@
 
         install(APK_LOCATION_29v4);
 
-        eventually(() -> assertThat(getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).named(
-                "foreground app-op").isEqualTo(MODE_FOREGROUND));
+        eventually(() -> assertWithMessage("foreground app-op").that(
+                getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_FOREGROUND));
     }
 
     /**
@@ -141,8 +141,8 @@
         install(APK_LOCATION_BACKGROUND_29);
 
         // Wait until the system sets the app-op automatically
-        eventually(() -> assertThat(getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).named(
-                "loc app-op").isEqualTo(MODE_IGNORED));
+        eventually(() -> assertWithMessage("loc app-op").that(
+                getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_IGNORED));
     }
 
     /**
@@ -157,8 +157,8 @@
         sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_COARSE_LOCATION);
 
         // Wait until the system sets the app-op automatically
-        eventually(() -> assertThat(getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).named(
-                "loc app-op").isEqualTo(MODE_FOREGROUND));
+        eventually(() -> assertWithMessage("loc app-op").that(
+                getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_FOREGROUND));
     }
 
     /**
@@ -174,8 +174,8 @@
         sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
 
         // Wait until the system sets the app-op automatically
-        eventually(() -> assertThat(getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).named(
-                "loc app-op").isEqualTo(MODE_ALLOWED));
+        eventually(() -> assertWithMessage("loc app-op").that(
+                getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_ALLOWED));
     }
 
     /**
@@ -191,8 +191,8 @@
 
         // Wait until the system sets the app-op automatically
         // Fine location uses background location to limit access
-        eventually(() -> assertThat(getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).named(
-                "loc app-op").isEqualTo(MODE_FOREGROUND));
+        eventually(() -> assertWithMessage("loc app-op").that(
+                getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_FOREGROUND));
     }
 
     /**
@@ -208,8 +208,8 @@
         sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
 
         // Wait until the system sets the app-op automatically
-        eventually(() -> assertThat(getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).named(
-                "loc app-op").isEqualTo(MODE_ALLOWED));
+        eventually(() -> assertWithMessage("loc app-op").that(
+                getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_ALLOWED));
     }
 
     /**
@@ -225,8 +225,8 @@
         sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_COARSE_LOCATION);
 
         // Wait until the system sets the app-op automatically
-        eventually(() -> assertThat(getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).named(
-                "loc app-op").isEqualTo(MODE_FOREGROUND));
+        eventually(() -> assertWithMessage("loc app-op").that(
+                getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_FOREGROUND));
     }
 
     /**
@@ -244,7 +244,7 @@
         sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
 
         // Wait until the system sets the app-op automatically
-        eventually(() -> assertThat(getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).named(
-                "loc app-op").isEqualTo(MODE_ALLOWED));
+        eventually(() -> assertWithMessage("loc app-op").that(
+                getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_ALLOWED));
     }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java b/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
index 0926a0f..b44d2cb 100644
--- a/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
@@ -18,6 +18,8 @@
 import android.os.PowerManager;
 import android.test.AndroidTestCase;
 
+import java.time.Duration;
+
 public class PowerManagerServicePermissionTest extends AndroidTestCase {
 
     public void testSetBatterySaver_requiresPermissions() {
@@ -42,7 +44,7 @@
         }
     }
 
-    public void testsetDynamicPowerSavings_requiresPermissions() {
+    public void testSetDynamicPowerSavings_requiresPermissions() {
         try {
             PowerManager manager = getContext().getSystemService(PowerManager.class);
             manager.setDynamicPowerSaveHint(true, 0);
@@ -51,4 +53,14 @@
             // Expected Exception
         }
     }
+
+    public void testSetBatteryDischargePrediction_requiresPermissions() {
+        try {
+            PowerManager manager = getContext().getSystemService(PowerManager.class);
+            manager.setBatteryDischargePrediction(Duration.ofMillis(1000), false);
+            fail("Updating the discharge prediction requires the DEVICE_POWER permission");
+        } catch (SecurityException e) {
+            // Expected Exception
+        }
+    }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
index b53bf6a..1e00120 100644
--- a/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
@@ -37,6 +37,7 @@
 import static com.android.compatibility.common.util.SystemUtil.eventually;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
 
@@ -84,6 +85,8 @@
             TMP_DIR + "CtsAppThatRequestsLocationPermission28.apk";
     private static final String APK_LOCATION_22 =
             TMP_DIR + "CtsAppThatRequestsLocationPermission22.apk";
+    private static final String APK_LOCATION_BACKGROUND_28 =
+            TMP_DIR + "CtsAppThatRequestsLocationAndBackgroundPermission28.apk";
     private static final String APK_LOCATION_BACKGROUND_29 =
             TMP_DIR + "CtsAppThatRequestsLocationAndBackgroundPermission29.apk";
     private static final String APK_SHARED_UID_LOCATION_29 =
@@ -118,7 +121,8 @@
      * @param permName The permission that needs to be granted
      */
     private void assertPermissionGranted(@NonNull String permName) throws Exception {
-        eventually(() -> assertThat(isGranted(APP_PKG, permName)).named(permName + " is granted").isTrue());
+        eventually(() -> assertWithMessage(permName + " is granted").that(
+                isGranted(APP_PKG, permName)).isTrue());
     }
 
     /**
@@ -127,7 +131,7 @@
      * @param permName The permission that should not be granted
      */
     private void assertPermissionRevoked(@NonNull String permName) throws Exception {
-        assertThat(isGranted(APP_PKG, permName)).named(permName + " is granted").isFalse();
+        assertWithMessage(permName + " is granted").that(isGranted(APP_PKG, permName)).isFalse();
     }
 
     /**
@@ -477,8 +481,30 @@
 
         install(APK_LOCATION_BACKGROUND_29);
 
-        eventually(() -> assertThat(getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).named("foreground app-op")
-                .isEqualTo(MODE_FOREGROUND));
+        eventually(() -> assertWithMessage("foreground app-op").that(
+                getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_FOREGROUND));
+    }
+
+    /**
+     * An implicit permission should get revoked when the app gets updated and now requests the
+     * permission. This even happens if the app is not targeting the SDK the permission was split
+     * in.
+     */
+    @Test
+    public void newPermissionGetRevokedOnUpgradeBeforeSplitSDK() throws Exception {
+        install(APK_LOCATION_28);
+
+        // Background permission can only be granted together with foreground permission
+        grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+        grantPermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+        // Background location was introduced in SDK 29. Hence an app targeting 28 is usually
+        // unaware of this permission. If the app declares that it is aware by adding the permission
+        // in the manifest the permission will get revoked. This allows the app to request the
+        // permission from the user.
+        install(APK_LOCATION_BACKGROUND_28);
+
+        assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
     }
 
     /**
diff --git a/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java b/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
index 99fdadb..8ccd979 100755
--- a/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
@@ -20,10 +20,15 @@
 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
 import static android.Manifest.permission.ACCESS_MEDIA_LOCATION;
+import static android.Manifest.permission.BACKGROUND_CAMERA;
+import static android.Manifest.permission.CAMERA;
 import static android.Manifest.permission.READ_CALL_LOG;
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.READ_PHONE_STATE;
+import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import static android.Manifest.permission.RECORD_AUDIO;
+import static android.Manifest.permission.RECORD_BACKGROUND_AUDIO;
 import static android.Manifest.permission.WRITE_CALL_LOG;
 import static android.Manifest.permission.WRITE_CONTACTS;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
@@ -56,10 +61,6 @@
 
     private static final int NO_TARGET = Build.VERSION_CODES.CUR_DEVELOPMENT + 1;
 
-    // Redefined here since it's only present in the system API surface.
-    private static final String READ_PRIVILEGED_PHONE_STATE =
-            "android.permission.READ_PRIVILEGED_PHONE_STATE";
-
     private List<SplitPermissionInfo> mSplitPermissions;
 
     @Before
@@ -116,10 +117,18 @@
                 case READ_PRIVILEGED_PHONE_STATE:
                     assertSplit(split, READ_PHONE_STATE, NO_TARGET);
                     break;
+                case RECORD_AUDIO:
+                    // Written this way so that when the sdk int is set for S the test fails
+                    // When this fails verify that the targetSdk is correct in platform.xml
+                    assertSplit(split, RECORD_BACKGROUND_AUDIO, Build.VERSION_CODES.S - 9969);
+                    break;
+                case CAMERA:
+                    assertSplit(split, BACKGROUND_CAMERA, Build.VERSION_CODES.S - 9969);
+                    break;
             }
         }
 
-        assertEquals(8, seenSplits.size());
+        assertEquals(10, seenSplits.size());
     }
 
     private void assertSplit(SplitPermissionInfo split, String permission, int targetSdk) {
diff --git a/tests/tests/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java b/tests/tests/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java
index b13a00a..53fb783 100644
--- a/tests/tests/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java
+++ b/tests/tests/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java
@@ -181,25 +181,6 @@
         assertEquals(audioMode, mAudioManager.getMode());
     }
 
-    /**
-     * Verify that TelephonyManager.setDataEnabled requires Permission.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#MODIFY_PHONE_STATE}.
-     */
-    @Test
-    public void testSetDataEnabled() {
-        if (!mHasTelephony) {
-            return;
-        }
-        try {
-            mTelephonyManager.setDataEnabled(false);
-            fail("Able to set data enabled");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
      /**
      * Tests that isManualNetworkSelectionAllowed requires permission
      * Expects a security exception since the caller does not have carrier privileges.
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index e3bcea42..8f56234 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -99,11 +99,11 @@
     <protected-broadcast android:name="android.intent.action.LOAD_DATA" />
 
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
-    <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" />
     <protected-broadcast android:name="android.os.action.DEVICE_IDLE_MODE_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_WHITELIST_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL" />
+    <protected-broadcast android:name="android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED" />
 
     <!-- @deprecated This is rarely used and will be phased out soon. -->
     <protected-broadcast android:name="android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED" />
@@ -1227,8 +1227,19 @@
         android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_recordAudio"
         android:description="@string/permdesc_recordAudio"
+        android:backgroundPermission="android.permission.RECORD_BACKGROUND_AUDIO"
         android:protectionLevel="dangerous|instant" />
 
+    <!-- Allows an application to record audio while in the background.
+         <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.RECORD_BACKGROUND_AUDIO"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_recordBackgroundAudio"
+        android:description="@string/permdesc_recordBackgroundAudio"
+        android:permissionFlags="hardRestricted|installerExemptIgnored"
+        android:protectionLevel="dangerous" />
+
     <!-- ====================================================================== -->
     <!-- Permissions for activity recognition                        -->
     <!-- ====================================================================== -->
@@ -1296,8 +1307,19 @@
         android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_camera"
         android:description="@string/permdesc_camera"
+        android:backgroundPermission="android.permission.BACKGROUND_CAMERA"
         android:protectionLevel="dangerous|instant" />
 
+    <!-- Required to be able to access the camera device in the background.
+         <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.BACKGROUND_CAMERA"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_backgroundCamera"
+        android:description="@string/permdesc_backgroundCamera"
+        android:permissionFlags="hardRestricted|installerExemptIgnored"
+        android:protectionLevel="dangerous" />
+
       <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
            system only camera devices.
            <p>Protection level: system|signature
@@ -1536,6 +1558,13 @@
     <permission android:name="android.permission.INSTALL_LOCATION_PROVIDER"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows an application to install a LocationTimeZoneProvider into the
+         LocationTimeZoneProviderManager.
+         <p>Not for use by third-party applications.
+    -->
+    <permission android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi @hide Allows HDMI-CEC service to access device and configuration files.
          This should only be used by HDMI-CEC service.
     -->
@@ -3415,6 +3444,14 @@
     <permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- Must be required by a android.service.musicrecognition.MusicRecognitionService,
+         to ensure that only the system can bind to it.
+         @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_MUSIC_RECOGNITION_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- Must be required by a android.service.contentcapture.ContentCaptureService,
          to ensure that only the system can bind to it.
          @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -3529,6 +3566,14 @@
     <permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID"
          android:protectionLevel="signature" />
 
+    <!-- This permission is required by Media Resource Observer Service when
+         accessing its registerObserver Api.
+         <p>Protection level: signature|privileged
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER"
+         android:protectionLevel="signature|privileged" />
+
     <!-- Must be required by a {@link android.media.routing.MediaRouteService}
          to ensure that only the system can interact with it.
          @hide -->
@@ -3955,6 +4000,15 @@
     <permission android:name="android.permission.CAPTURE_AUDIO_HOTWORD"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Puts an application in the chain of trust for sound trigger
+         operations. Being in the chain of trust allows an application to
+         delegate an identity of a separate entity to the sound trigger system
+         and vouch for the authenticity of this identity.
+         <p>Not for use by third-party applications.</p>
+         @hide -->
+    <permission android:name="android.permission.SOUNDTRIGGER_DELEGATE_IDENTITY"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows an application to modify audio routing and override policy decisions.
          <p>Not for use by third-party applications.</p>
          @hide -->
@@ -4280,6 +4334,10 @@
     <permission android:name="android.permission.WRITE_DREAM_STATE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @hide Allows applications to read whether ambient display is suppressed. -->
+    <permission android:name="android.permission.READ_DREAM_SUPPRESSION"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allow an application to read and write the cache partition.
          @hide -->
     <permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM"
@@ -4439,6 +4497,12 @@
     <permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT"
         android:protectionLevel="signature" />
 
+    <!-- Allows access to TestApis for various components in the biometric stack, including
+         FingerprintService, FaceService, BiometricService. Used by com.android.server.biometrics
+         CTS tests. @hide @TestApi -->
+    <permission android:name="android.permission.TEST_BIOMETRIC"
+        android:protectionLevel="signature" />
+
     <!-- Allows direct access to the <Biometric>Service interfaces. Reserved for the system. @hide -->
     <permission android:name="android.permission.MANAGE_BIOMETRIC"
         android:protectionLevel="signature" />
@@ -4451,10 +4515,6 @@
     <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
         android:protectionLevel="signature" />
 
-    <!-- Allows an app to reset face authentication attempt counter. Reserved for the system. @hide -->
-    <permission android:name="android.permission.RESET_FACE_LOCKOUT"
-        android:protectionLevel="signature" />
-
     <!-- Allows an application to control keyguard.  Only allowed for system processes.
         @hide -->
     <permission android:name="android.permission.CONTROL_KEYGUARD"
@@ -4675,6 +4735,12 @@
     <permission android:name="android.permission.HANDLE_CAR_MODE_CHANGES"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows the holder to send category_car notifications.
+        @hide -->
+    <permission
+        android:name="android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"
+        android:protectionLevel="signature|privileged" />
+
     <!-- The system process is explicitly the only one allowed to launch the
          confirmation UI for full backup/restore -->
     <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
@@ -4778,6 +4844,11 @@
     <permission android:name="android.permission.MANAGE_AUTO_FILL"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to invoke the music recognition service.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to manage the content capture service.
          @hide  <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.MANAGE_CONTENT_CAPTURE"
@@ -4993,6 +5064,11 @@
                 android:protectionLevel="normal" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
+    <!-- @hide @TestApi Allows apps to reset the state of {@link com.android.server.am.AppErrors}.
+         Only for use in CTS tests. -->
+    <permission android:name="android.permission.RESET_APP_ERRORS"
+                android:protectionLevel="signature" />
+
     <!-- @hide Allow the caller to collect debugging data from processes that otherwise
         would require USAGE_STATS. Before sharing this data with other apps, holders
         of this permission are REQUIRED to themselves check that the caller has
@@ -5045,6 +5121,7 @@
                 android:relinquishTaskIdentity="true"
                 android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
                 android:process=":ui"
+                android:exported="true"
                 android:visibleToInstantApps="true">
             <intent-filter>
                 <action android:name="android.intent.action.CHOOSER" />
@@ -5156,6 +5233,7 @@
         <activity android:name="com.android.internal.app.ShutdownActivity"
             android:permission="android.permission.SHUTDOWN"
             android:theme="@style/Theme.NoDisplay"
+            android:exported="true"
             android:excludeFromRecents="true">
             <intent-filter>
                 <action android:name="com.android.internal.intent.action.REQUEST_SHUTDOWN" />
@@ -5177,6 +5255,7 @@
                   android:enabled="false"
                   android:process=":ui"
                   android:systemUserOnly="true"
+                  android:exported="true"
                   android:theme="@style/Theme.Translucent.NoTitleBar">
             <intent-filter android:priority="-100">
                 <action android:name="android.intent.action.MAIN" />
@@ -5189,6 +5268,7 @@
         <activity android:name="com.android.internal.app.ConfirmUserCreationActivity"
                 android:excludeFromRecents="true"
                 android:process=":ui"
+                android:exported="true"
                 android:theme="@style/Theme.Dialog.Confirmation">
             <intent-filter android:priority="1000">
                 <action android:name="android.os.action.CREATE_USER" />
@@ -5228,6 +5308,7 @@
         </activity>
 
         <receiver android:name="com.android.server.BootReceiver"
+                android:exported="true"
                 android:systemUserOnly="true">
             <intent-filter android:priority="1000">
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
@@ -5235,6 +5316,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.CertPinInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_PINS" />
@@ -5243,6 +5325,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.IntentFirewallInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_INTENT_FIREWALL" />
@@ -5251,6 +5334,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.SmsShortCodesInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_SMS_SHORT_CODES" />
@@ -5259,6 +5343,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver"
+                android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" />
@@ -5267,6 +5352,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.ApnDbInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="com.android.internal.intent.action.UPDATE_APN_DB" />
@@ -5275,6 +5361,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.CarrierProvisioningUrlsInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS" />
@@ -5283,6 +5370,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.CertificateTransparencyLogInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_CT_LOGS" />
@@ -5291,6 +5379,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.LangIdInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_LANG_ID" />
@@ -5299,6 +5388,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.SmartSelectionInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_SMART_SELECTION" />
@@ -5307,6 +5397,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.ConversationActionsInstallReceiver"
+                android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_CONVERSATION_ACTIONS" />
@@ -5315,6 +5406,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.CarrierIdInstallReceiver"
+                android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.os.action.UPDATE_CARRIER_ID_DB" />
@@ -5323,6 +5415,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.EmergencyNumberDbInstallReceiver"
+                android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.os.action.UPDATE_EMERGENCY_NUMBER_DB" />
@@ -5331,6 +5424,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.MasterClearReceiver"
+                android:exported="true"
             android:permission="android.permission.MASTER_CLEAR">
             <intent-filter
                     android:priority="100" >
@@ -5347,6 +5441,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.WallpaperUpdateReceiver"
+                android:exported="true"
                   android:permission="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY">
             <intent-filter>
                 <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
@@ -5439,7 +5534,8 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
-        <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader">
+        <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.LOAD_DATA" />
             </intent-filter>
diff --git a/tests/tests/permission2/res/raw/automotive_android_manifest.xml b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
index dd37ac9..b9d182c 100644
--- a/tests/tests/permission2/res/raw/automotive_android_manifest.xml
+++ b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
@@ -350,135 +350,125 @@
          android:label="@string/car_permission_label_set_car_vendor_category_10"
          android:description="@string/car_permission_desc_set_car_vendor_category_10"/>
 
-    <permission
-        android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_audio_volume"
-        android:description="@string/car_permission_desc_audio_volume" />
+    <permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_audio_volume"
+         android:description="@string/car_permission_desc_audio_volume"/>
 
-    <permission
-        android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_audio_settings"
-        android:description="@string/car_permission_desc_audio_settings" />
+    <permission android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_audio_settings"
+         android:description="@string/car_permission_desc_audio_settings"/>
 
-    <permission
-        android:name="android.car.permission.RECEIVE_CAR_AUDIO_DUCKING_EVENTS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_receive_ducking"
-        android:description="@string/car_permission_desc_receive_ducking" />
+    <permission android:name="android.car.permission.RECEIVE_CAR_AUDIO_DUCKING_EVENTS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_receive_ducking"
+         android:description="@string/car_permission_desc_receive_ducking"/>
 
-    <permission
-        android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"
-        android:protectionLevel="signature"
-        android:label="@string/car_permission_label_bind_instrument_cluster_rendering"
-        android:description="@string/car_permission_desc_bind_instrument_cluster_rendering"/>
+    <permission android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"
+         android:protectionLevel="signature"
+         android:label="@string/car_permission_label_bind_instrument_cluster_rendering"
+         android:description="@string/car_permission_desc_bind_instrument_cluster_rendering"/>
 
-    <permission
-        android:name="android.car.permission.BIND_CAR_INPUT_SERVICE"
-        android:protectionLevel="signature"
-        android:label="@string/car_permission_label_bind_input_service"
-        android:description="@string/car_permission_desc_bind_input_service"/>
+    <permission android:name="android.car.permission.BIND_CAR_INPUT_SERVICE"
+         android:protectionLevel="signature"
+         android:label="@string/car_permission_label_bind_input_service"
+         android:description="@string/car_permission_desc_bind_input_service"/>
 
-    <permission
-        android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_car_display_in_cluster"
-        android:description="@string/car_permission_desc_car_display_in_cluster" />
+    <permission android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_car_display_in_cluster"
+         android:description="@string/car_permission_desc_car_display_in_cluster"/>
 
-    <permission
-        android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_car_cluster_control"
-        android:description="@string/car_permission_desc_car_cluster_control" />
+    <permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_car_cluster_control"
+         android:description="@string/car_permission_desc_car_cluster_control"/>
 
-    <permission
-        android:name="android.car.permission.CAR_HANDLE_USB_AOAP_DEVICE"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_handle_usb_aoap_device"
-        android:description="@string/car_permission_desc_car_handle_usb_aoap_device" />
+    <permission android:name="android.car.permission.CAR_HANDLE_USB_AOAP_DEVICE"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_handle_usb_aoap_device"
+         android:description="@string/car_permission_desc_car_handle_usb_aoap_device"/>
 
-    <permission
-        android:name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_ux_restrictions_configuration"
-        android:description="@string/car_permission_desc_car_ux_restrictions_configuration" />
+    <permission android:name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_ux_restrictions_configuration"
+         android:description="@string/car_permission_desc_car_ux_restrictions_configuration"/>
 
-    <permission
-        android:name="android.car.permission.STORAGE_MONITORING"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_storage_monitoring"
-        android:description="@string/car_permission_desc_storage_monitoring" />
+    <permission android:name="android.car.permission.STORAGE_MONITORING"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_storage_monitoring"
+         android:description="@string/car_permission_desc_storage_monitoring"/>
 
-    <permission
-        android:name="android.car.permission.CAR_ENROLL_TRUST"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_enroll_trust"
-        android:description="@string/car_permission_desc_enroll_trust" />
+    <permission android:name="android.car.permission.CAR_ENROLL_TRUST"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_enroll_trust"
+         android:description="@string/car_permission_desc_enroll_trust"/>
 
-    <permission
-        android:name="android.car.permission.CAR_TEST_SERVICE"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_test_service"
-        android:description="@string/car_permission_desc_car_test_service" />
+    <permission android:name="android.car.permission.CAR_TEST_SERVICE"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_test_service"
+         android:description="@string/car_permission_desc_car_test_service"/>
 
-    <uses-permission android:name="android.permission.CALL_PHONE" />
-    <uses-permission android:name="android.permission.DEVICE_POWER" />
-    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
-    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
-    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
-    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
-    <uses-permission android:name="android.permission.READ_CALL_LOG" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
-    <uses-permission android:name="android.permission.REBOOT" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-    <uses-permission android:name="android.permission.REMOVE_TASKS" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.MANAGE_USERS" />
-    <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
+    <uses-permission android:name="android.permission.CALL_PHONE"/>
+    <uses-permission android:name="android.permission.DEVICE_POWER"/>
+    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING"/>
+    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.REAL_GET_TASKS"/>
+    <uses-permission android:name="android.permission.REBOOT"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+    <uses-permission android:name="android.permission.REMOVE_TASKS"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.MANAGE_USERS"/>
+    <uses-permission android:name="android.permission.LOCATION_HARDWARE"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT"/>
 
     <application android:label="@string/app_title"
-                 android:directBootAware="true"
-                 android:allowBackup="false"
-                 android:persistent="true">
+         android:directBootAware="true"
+         android:allowBackup="false"
+         android:persistent="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <service android:name=".CarService"
-                android:singleUser="true">
+             android:singleUser="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.car.ICar" />
+                <action android:name="android.car.ICar"/>
             </intent-filter>
         </service>
-        <service android:name=".PerUserCarService" android:exported="false" />
+        <service android:name=".PerUserCarService"
+             android:exported="false"/>
 
-        <service
-            android:name="com.android.car.trust.CarBleTrustAgent"
-            android:permission="android.permission.BIND_TRUST_AGENT"
-            android:singleUser="true">
+        <service android:name="com.android.car.trust.CarBleTrustAgent"
+             android:permission="android.permission.BIND_TRUST_AGENT"
+             android:singleUser="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.trust.TrustAgentService" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.service.trust.TrustAgentService"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- Warning: the meta data must be included if the service is direct boot aware.
-                If not included, the device will crash before boot completes. Rendering the
-                device unusable. -->
+                                If not included, the device will crash before boot completes. Rendering the
+                                device unusable. -->
             <meta-data android:name="android.service.trust.trustagent"
-                       android:resource="@xml/car_trust_agent"/>
+                 android:resource="@xml/car_trust_agent"/>
         </service>
         <activity android:name="com.android.car.pm.ActivityBlockingActivity"
-                  android:excludeFromRecents="true"
-                  android:theme="@android:style/Theme.Translucent.NoTitleBar"
-                  android:exported="false"
-                  android:launchMode="singleTask">
+             android:excludeFromRecents="true"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"
+             android:exported="false"
+             android:launchMode="singleTask">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 1140cfb..07fd26d 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -20,7 +20,7 @@
 import static android.content.pm.PermissionInfo.PROTECTION_MASK_BASE;
 import static android.os.Build.VERSION.SECURITY_PATCH;
 
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.content.Context;
 import android.content.pm.PackageInfo;
@@ -235,7 +235,7 @@
         }
 
         // Fail on any offending item
-        assertThat(offendingList).named("list of offending permissions").isEmpty();
+        assertWithMessage("list of offending permissions").that(offendingList).isEmpty();
     }
 
     private List<ExpectedPermissionInfo> loadExpectedPermissions(int resourceId) throws Exception {
@@ -331,6 +331,9 @@
                 case "softRestricted": {
                     protectionFlags |= PermissionInfo.FLAG_SOFT_RESTRICTED;
                 } break;
+                case "installerExemptIgnored": {
+                    protectionFlags |= PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED;
+                } break;
             }
         }
         return protectionFlags;
diff --git a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
index b44cfcb..11e6121 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
@@ -26,6 +26,7 @@
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.fail;
 
@@ -490,7 +491,7 @@
     private void assertRestrictedPermissionWhitelisted(
             @NonNull Set<String> expectedWhitelistedPermissions) throws Exception {
         final PackageManager packageManager = getContext().getPackageManager();
-    eventually(() -> runWithShellPermissionIdentity(() -> {
+        eventually(() -> runWithShellPermissionIdentity(() -> {
             final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
             final PackageInfo packageInfo = packageManager.getPackageInfo(PKG,
                     PackageManager.GET_PERMISSIONS);
@@ -502,7 +503,7 @@
                         | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE);
 
             assertThat(whitelistedPermissions).isNotNull();
-            assertThat(whitelistedPermissions).named("Whitelisted permissions")
+            assertWithMessage("Whitelisted permissions").that(whitelistedPermissions)
                     .containsExactlyElementsIn(expectedWhitelistedPermissions);
 
             // Also assert that apps ops are properly set
@@ -540,8 +541,8 @@
                     }
                 }
 
-                assertThat(appOpsManager.unsafeCheckOpRawNoThrow(op,
-                        packageInfo.applicationInfo.uid, PKG)).named(op).isIn(possibleModes);
+                assertWithMessage(op).that(appOpsManager.unsafeCheckOpRawNoThrow(op,
+                        packageInfo.applicationInfo.uid, PKG)).isIn(possibleModes);
             }
         }));
     }
diff --git a/tests/tests/permission2/src/android/permission2/cts/RestrictedStoragePermissionSharedUidTest.java b/tests/tests/permission2/src/android/permission2/cts/RestrictedStoragePermissionSharedUidTest.java
index 044abe2..5fb56e8 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RestrictedStoragePermissionSharedUidTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/RestrictedStoragePermissionSharedUidTest.java
@@ -28,7 +28,7 @@
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static java.lang.Integer.min;
 
@@ -99,8 +99,8 @@
          * @param expectGranted {@code true} if the permission is expected to be granted
          */
         void assertStoragePermGranted(boolean expectGranted) {
-            eventually(() -> assertThat(isGranted(mPkg, READ_EXTERNAL_STORAGE)).named(
-                    this + " read storage granted").isEqualTo(expectGranted));
+            eventually(() -> assertWithMessage(this + " read storage granted").that(
+                    isGranted(mPkg, READ_EXTERNAL_STORAGE)).isEqualTo(expectGranted));
         }
 
         /**
@@ -112,11 +112,13 @@
             eventually(() -> runWithShellPermissionIdentity(() -> {
                 int uid = sContext.getPackageManager().getPackageUid(mPkg, 0);
                 if (expectHasNotIsolatedStorage) {
-                    assertThat(sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
-                            mPkg)).named(this + " legacy storage mode").isEqualTo(MODE_ALLOWED);
+                    assertWithMessage(this + " legacy storage mode").that(
+                            sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
+                            mPkg)).isEqualTo(MODE_ALLOWED);
                 } else {
-                    assertThat(sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
-                            mPkg)).named(this + " legacy storage mode").isNotEqualTo(MODE_ALLOWED);
+                    assertWithMessage(this + " legacy storage mode").that(
+                            sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
+                            mPkg)).isNotEqualTo(MODE_ALLOWED);
                 }
             }));
         }
diff --git a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
index 26c6cc8..c54a96c 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
+++ b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
@@ -56,6 +56,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -75,14 +76,14 @@
             platformRuntimePerms.filter { !platformBgPermNames.contains(it.name) }
 
         for (perm in platformFgPerms) {
-            assertThat(permissionToOp(perm.name)).named("AppOp for ${perm.name}").isNotNull()
+            assertWithMessage("AppOp for ${perm.name}").that(permissionToOp(perm.name)).isNotNull()
         }
     }
 
     @Test
     fun groupOfRuntimePermissionsShouldBeUnknown() {
         for (perm in platformRuntimePerms) {
-            assertThat(perm.group).named("Group of ${perm.name}").isEqualTo(UNDEFINED)
+            assertWithMessage("Group of ${perm.name}").that(perm.group).isEqualTo(UNDEFINED)
         }
     }
 
@@ -96,7 +97,7 @@
                 }
 
         for (perm in platformAppOpPerms) {
-            assertThat(permissionToOp(perm.name)).named("AppOp for ${perm.name}").isNotNull()
+            assertWithMessage("AppOp for ${perm.name}").that(permissionToOp(perm.name)).isNotNull()
         }
     }
 
@@ -109,7 +110,7 @@
             platformRuntimePerms.filter { platformBgPermNames.contains(it.name) }
 
         for (perm in platformBgPerms) {
-            assertThat(permissionToOp(perm.name)).named("AppOp for ${perm.name}").isNull()
+            assertWithMessage("AppOp for ${perm.name}").that(permissionToOp(perm.name)).isNull()
         }
     }
 
diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp
index 576e1ed..325d27d 100644
--- a/tests/tests/permission3/Android.bp
+++ b/tests/tests/permission3/Android.bp
@@ -32,13 +32,16 @@
         ":CtsPermissionPolicyApp25",
         ":CtsUsePermissionApp22",
         ":CtsUsePermissionApp22CalendarOnly",
+        ":CtsUsePermissionApp22None",
         ":CtsUsePermissionApp23",
         ":CtsUsePermissionApp25",
         ":CtsUsePermissionApp26",
         ":CtsUsePermissionApp28",
         ":CtsUsePermissionApp29",
+        ":CtsUsePermissionApp30",
+        ":CtsUsePermissionApp30WithBackground",
         ":CtsUsePermissionAppLatest",
-        ":CtsUsePermissionAppLatestWithBackground",
+        ":CtsUsePermissionAppLatestNone",
         ":CtsUsePermissionAppWithOverlay",
     ],
     test_suites: [
diff --git a/tests/tests/permission3/AndroidTest.xml b/tests/tests/permission3/AndroidTest.xml
index 557de85..a4f23ac 100644
--- a/tests/tests/permission3/AndroidTest.xml
+++ b/tests/tests/permission3/AndroidTest.xml
@@ -38,13 +38,16 @@
         <option name="push" value="CtsPermissionPolicyApp25.apk->/data/local/tmp/cts/permission3/CtsPermissionPolicyApp25.apk" />
         <option name="push" value="CtsUsePermissionApp22.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22.apk" />
         <option name="push" value="CtsUsePermissionApp22CalendarOnly.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22CalendarOnly.apk" />
+        <option name="push" value="CtsUsePermissionApp22None.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22None.apk" />
         <option name="push" value="CtsUsePermissionApp23.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp23.apk" />
         <option name="push" value="CtsUsePermissionApp25.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp25.apk" />
         <option name="push" value="CtsUsePermissionApp26.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp26.apk" />
         <option name="push" value="CtsUsePermissionApp28.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp28.apk" />
         <option name="push" value="CtsUsePermissionApp29.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp29.apk" />
+        <option name="push" value="CtsUsePermissionApp30.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30.apk" />
+        <option name="push" value="CtsUsePermissionApp30WithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBackground.apk" />
         <option name="push" value="CtsUsePermissionAppLatest.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatest.apk" />
-        <option name="push" value="CtsUsePermissionAppLatestWithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestWithBackground.apk" />
+        <option name="push" value="CtsUsePermissionAppLatestNone.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestNone.apk" />
         <option name="push" value="CtsUsePermissionAppWithOverlay.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppWithOverlay.apk" />
     </target_preparer>
 
diff --git a/tests/tests/permission3/UsePermissionApp22None/Android.bp b/tests/tests/permission3/UsePermissionApp22None/Android.bp
new file mode 100644
index 0000000..9437fd1
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp22None/Android.bp
@@ -0,0 +1,27 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsUsePermissionApp22None",
+    srcs: [
+        ":CtsUsePermissionAppSrc",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+    min_sdk_version: "22",
+}
diff --git a/tests/tests/permission3/UsePermissionApp22None/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp22None/AndroidManifest.xml
new file mode 100644
index 0000000..6145204
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp22None/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission3.cts.usepermission">
+
+    <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22" />
+
+    <application>
+        <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+        <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionApp30/Android.bp b/tests/tests/permission3/UsePermissionApp30/Android.bp
new file mode 100644
index 0000000..c584183
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp30/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsUsePermissionApp30",
+    srcs: [
+        ":CtsUsePermissionAppSrc",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+
+    min_sdk_version: "30",
+}
diff --git a/tests/tests/permission3/UsePermissionApp30/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp30/AndroidManifest.xml
new file mode 100644
index 0000000..03654c0
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp30/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission3.cts.usepermission">
+
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+
+    <!-- Request two different permissions within the same group -->
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+    <uses-permission android:name="android.permission.CAMERA" />
+
+    <application>
+        <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+        <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionApp30WithBackground/Android.bp b/tests/tests/permission3/UsePermissionApp30WithBackground/Android.bp
new file mode 100644
index 0000000..5c9fc03
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp30WithBackground/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsUsePermissionApp30WithBackground",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+
+    min_sdk_version: "30",
+}
diff --git a/tests/tests/permission3/UsePermissionApp30WithBackground/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp30WithBackground/AndroidManifest.xml
new file mode 100644
index 0000000..3d41276
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp30WithBackground/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission3.cts.usepermission">
+
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+    <application>
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppLatestWithBackground/src/android/permission3/cts/usepermission/RequestPermissionsActivity.kt b/tests/tests/permission3/UsePermissionApp30WithBackground/src/android/permission3/cts/usepermission/RequestPermissionsActivity.kt
similarity index 100%
rename from tests/tests/permission3/UsePermissionAppLatestWithBackground/src/android/permission3/cts/usepermission/RequestPermissionsActivity.kt
rename to tests/tests/permission3/UsePermissionApp30WithBackground/src/android/permission3/cts/usepermission/RequestPermissionsActivity.kt
diff --git a/tests/tests/permission3/UsePermissionAppLatestNone/Android.bp b/tests/tests/permission3/UsePermissionAppLatestNone/Android.bp
new file mode 100644
index 0000000..f5b0297
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLatestNone/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsUsePermissionAppLatestNone",
+    srcs: [
+        ":CtsUsePermissionAppSrc",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+}
diff --git a/tests/tests/permission3/UsePermissionAppLatestNone/AndroidManifest.xml b/tests/tests/permission3/UsePermissionAppLatestNone/AndroidManifest.xml
new file mode 100644
index 0000000..fa0310e
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLatestNone/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission3.cts.usepermission">
+
+    <application>
+        <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+        <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppLatestWithBackground/Android.bp b/tests/tests/permission3/UsePermissionAppLatestWithBackground/Android.bp
deleted file mode 100644
index 65bc605..0000000
--- a/tests/tests/permission3/UsePermissionAppLatestWithBackground/Android.bp
+++ /dev/null
@@ -1,26 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-android_test_helper_app {
-    name: "CtsUsePermissionAppLatestWithBackground",
-    srcs: [
-        "src/**/*.kt",
-    ],
-    static_libs: [
-        "kotlin-stdlib",
-    ],
-    certificate: ":cts-testkey2",
-}
diff --git a/tests/tests/permission3/UsePermissionAppLatestWithBackground/AndroidManifest.xml b/tests/tests/permission3/UsePermissionAppLatestWithBackground/AndroidManifest.xml
deleted file mode 100644
index adf2eac..0000000
--- a/tests/tests/permission3/UsePermissionAppLatestWithBackground/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.permission3.cts.usepermission">
-
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
-
-    <application>
-        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
-    </application>
-</manifest>
diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
index 3af1b50..0c59dc5 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
@@ -27,7 +27,7 @@
 import android.support.test.uiautomator.BySelector
 import android.support.test.uiautomator.UiDevice
 import android.support.test.uiautomator.UiObject2
-import androidx.test.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.ActivityTestRule
 import com.android.compatibility.common.util.SystemUtil.runShellCommand
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
@@ -127,7 +127,7 @@
         return UiAutomatorUtils.waitFindObject(selector, timeoutMillis)
     }
 
-    protected fun click(selector: BySelector, timeoutMillis: Long = 10_000) {
+    protected fun click(selector: BySelector, timeoutMillis: Long = 20_000) {
         waitFindObject(selector, timeoutMillis).click()
         waitForIdle()
     }
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index bea7a16..b0b7130 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -45,14 +45,17 @@
         const val APP_APK_PATH_22 = "$APK_DIRECTORY/CtsUsePermissionApp22.apk"
         const val APP_APK_PATH_22_CALENDAR_ONLY =
             "$APK_DIRECTORY/CtsUsePermissionApp22CalendarOnly.apk"
+        const val APP_APK_PATH_22_NONE = "$APK_DIRECTORY/CtsUsePermissionApp22None.apk"
         const val APP_APK_PATH_23 = "$APK_DIRECTORY/CtsUsePermissionApp23.apk"
         const val APP_APK_PATH_25 = "$APK_DIRECTORY/CtsUsePermissionApp25.apk"
         const val APP_APK_PATH_26 = "$APK_DIRECTORY/CtsUsePermissionApp26.apk"
         const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsUsePermissionApp28.apk"
         const val APP_APK_PATH_29 = "$APK_DIRECTORY/CtsUsePermissionApp29.apk"
+        const val APP_APK_PATH_30 = "$APK_DIRECTORY/CtsUsePermissionApp30.apk"
+        const val APP_APK_PATH_30_WITH_BACKGROUND =
+                "$APK_DIRECTORY/CtsUsePermissionApp30WithBackground.apk"
         const val APP_APK_PATH_LATEST = "$APK_DIRECTORY/CtsUsePermissionAppLatest.apk"
-        const val APP_APK_PATH_LATEST_WITH_BACKGROUND =
-                "$APK_DIRECTORY/CtsUsePermissionAppLatestWithBackground.apk"
+        const val APP_APK_PATH_LATEST_NONE = "$APK_DIRECTORY/CtsUsePermissionAppLatestNone.apk"
         const val APP_APK_PATH_WITH_OVERLAY = "$APK_DIRECTORY/CtsUsePermissionAppWithOverlay.apk"
         const val APP_PACKAGE_NAME = "android.permission3.cts.usepermission"
 
@@ -88,108 +91,54 @@
     protected val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
 
     private val platformResources = context.createPackageContext("android", 0).resources
-    private val permissionToLabelResNameMap =
-        if (!packageManager.arePermissionsIndividuallyControlled()) {
-            mapOf(
-                // Contacts
-                android.Manifest.permission.READ_CONTACTS
+    private val permissionToLabelResNameMap = mapOf(
+            // Contacts
+            android.Manifest.permission.READ_CONTACTS
                     to "@android:string/permgrouplab_contacts",
-                android.Manifest.permission.WRITE_CONTACTS
+            android.Manifest.permission.WRITE_CONTACTS
                     to "@android:string/permgrouplab_contacts",
-                // Calendar
-                android.Manifest.permission.READ_CALENDAR
+            // Calendar
+            android.Manifest.permission.READ_CALENDAR
                     to "@android:string/permgrouplab_calendar",
-                android.Manifest.permission.WRITE_CALENDAR
+            android.Manifest.permission.WRITE_CALENDAR
                     to "@android:string/permgrouplab_calendar",
-                // SMS
-                android.Manifest.permission.SEND_SMS to "@android:string/permgrouplab_sms",
-                android.Manifest.permission.RECEIVE_SMS to "@android:string/permgrouplab_sms",
-                android.Manifest.permission.READ_SMS to "@android:string/permgrouplab_sms",
-                android.Manifest.permission.RECEIVE_WAP_PUSH to "@android:string/permgrouplab_sms",
-                android.Manifest.permission.RECEIVE_MMS to "@android:string/permgrouplab_sms",
-                "android.permission.READ_CELL_BROADCASTS" to "@android:string/permgrouplab_sms",
-                // Storage
-                android.Manifest.permission.READ_EXTERNAL_STORAGE
+            // SMS
+            android.Manifest.permission.SEND_SMS to "@android:string/permgrouplab_sms",
+            android.Manifest.permission.RECEIVE_SMS to "@android:string/permgrouplab_sms",
+            android.Manifest.permission.READ_SMS to "@android:string/permgrouplab_sms",
+            android.Manifest.permission.RECEIVE_WAP_PUSH to "@android:string/permgrouplab_sms",
+            android.Manifest.permission.RECEIVE_MMS to "@android:string/permgrouplab_sms",
+            "android.permission.READ_CELL_BROADCASTS" to "@android:string/permgrouplab_sms",
+            // Storage
+            android.Manifest.permission.READ_EXTERNAL_STORAGE
                     to "@android:string/permgrouplab_storage",
-                android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+            android.Manifest.permission.WRITE_EXTERNAL_STORAGE
                     to "@android:string/permgrouplab_storage",
-                // Location
-                android.Manifest.permission.ACCESS_FINE_LOCATION
+            // Location
+            android.Manifest.permission.ACCESS_FINE_LOCATION
                     to "@android:string/permgrouplab_location",
-                android.Manifest.permission.ACCESS_COARSE_LOCATION
+            android.Manifest.permission.ACCESS_COARSE_LOCATION
                     to "@android:string/permgrouplab_location",
-                // Phone
-                android.Manifest.permission.READ_PHONE_STATE
+            // Phone
+            android.Manifest.permission.READ_PHONE_STATE
                     to "@android:string/permgrouplab_phone",
-                android.Manifest.permission.CALL_PHONE to "@android:string/permgrouplab_phone",
-                "android.permission.ACCESS_IMS_CALL_SERVICE"
+            android.Manifest.permission.CALL_PHONE to "@android:string/permgrouplab_phone",
+            "android.permission.ACCESS_IMS_CALL_SERVICE"
                     to "@android:string/permgrouplab_phone",
-                android.Manifest.permission.READ_CALL_LOG to "@android:string/permgrouplab_phone",
-                android.Manifest.permission.WRITE_CALL_LOG to "@android:string/permgrouplab_phone",
-                android.Manifest.permission.ADD_VOICEMAIL to "@android:string/permgrouplab_phone",
-                android.Manifest.permission.USE_SIP to "@android:string/permgrouplab_phone",
-                android.Manifest.permission.PROCESS_OUTGOING_CALLS
+            android.Manifest.permission.READ_CALL_LOG to "@android:string/permgrouplab_phone",
+            android.Manifest.permission.WRITE_CALL_LOG to "@android:string/permgrouplab_phone",
+            android.Manifest.permission.ADD_VOICEMAIL to "@android:string/permgrouplab_phone",
+            android.Manifest.permission.USE_SIP to "@android:string/permgrouplab_phone",
+            android.Manifest.permission.PROCESS_OUTGOING_CALLS
                     to "@android:string/permgrouplab_phone",
-                // Microphone
-                android.Manifest.permission.RECORD_AUDIO
+            // Microphone
+            android.Manifest.permission.RECORD_AUDIO
                     to "@android:string/permgrouplab_microphone",
-                // Camera
-                android.Manifest.permission.CAMERA to "@android:string/permgrouplab_camera",
-                // Body sensors
-                android.Manifest.permission.BODY_SENSORS to "@android:string/permgrouplab_sensors"
-            )
-        } else {
-            mapOf(
-                // Contacts
-                android.Manifest.permission.READ_CONTACTS to "@android:string/permlab_readContacts",
-                android.Manifest.permission.WRITE_CONTACTS
-                    to "@android:string/permlab_writeContacts",
-                // Calendar
-                android.Manifest.permission.READ_CALENDAR
-                    to "@android:string/permgrouplab_calendar",
-                android.Manifest.permission.WRITE_CALENDAR
-                    to "@android:string/permgrouplab_calendar",
-                // SMS
-                android.Manifest.permission.SEND_SMS to "@android:string/permlab_sendSms",
-                android.Manifest.permission.RECEIVE_SMS to "@android:string/permlab_receiveSms",
-                android.Manifest.permission.READ_SMS to "@android:string/permlab_readSms",
-                android.Manifest.permission.RECEIVE_WAP_PUSH
-                    to "@android:string/permlab_receiveWapPush",
-                android.Manifest.permission.RECEIVE_MMS to "@android:string/permlab_receiveMms",
-                "android.permission.READ_CELL_BROADCASTS"
-                    to "@android:string/permlab_readCellBroadcasts",
-                // Storage
-                android.Manifest.permission.READ_EXTERNAL_STORAGE
-                    to "@android:string/permgrouplab_storage",
-                android.Manifest.permission.WRITE_EXTERNAL_STORAGE
-                    to "@android:string/permgrouplab_storage",
-                // Location
-                android.Manifest.permission.ACCESS_FINE_LOCATION
-                    to "@android:string/permgrouplab_location",
-                android.Manifest.permission.ACCESS_COARSE_LOCATION
-                    to "@android:string/permgrouplab_location",
-                // Phone
-                android.Manifest.permission.READ_PHONE_STATE
-                    to "@android:string/permlab_readPhoneState",
-                android.Manifest.permission.CALL_PHONE to "@android:string/permlab_callPhone",
-                "android.permission.ACCESS_IMS_CALL_SERVICE"
-                    to "@android:string/permlab_accessImsCallService",
-                android.Manifest.permission.READ_CALL_LOG to "@android:string/permlab_readCallLog",
-                android.Manifest.permission.WRITE_CALL_LOG
-                    to "@android:string/permlab_writeCallLog",
-                android.Manifest.permission.ADD_VOICEMAIL to "@android:string/permlab_addVoicemail",
-                android.Manifest.permission.USE_SIP to "@android:string/permlab_use_sip",
-                android.Manifest.permission.PROCESS_OUTGOING_CALLS
-                    to "@android:string/permlab_processOutgoingCalls",
-                // Microphone
-                android.Manifest.permission.RECORD_AUDIO
-                    to "@android:string/permgrouplab_microphone",
-                // Camera
-                android.Manifest.permission.CAMERA to "@android:string/permgrouplab_camera",
-                // Body sensors
-                android.Manifest.permission.BODY_SENSORS to "@android:string/permgrouplab_sensors"
-            )
-        }
+            // Camera
+            android.Manifest.permission.CAMERA to "@android:string/permgrouplab_camera",
+            // Body sensors
+            android.Manifest.permission.BODY_SENSORS to "@android:string/permgrouplab_sensors"
+    )
 
     @Before
     @After
@@ -295,6 +244,11 @@
         val result = requestAppPermissions(*permissions, block = block)
         assertEquals(Activity.RESULT_OK, result.resultCode)
         assertEquals(
+            result.resultData!!.getStringArrayExtra("$APP_PACKAGE_NAME.PERMISSIONS")!!.size,
+            result.resultData!!.getIntArrayExtra("$APP_PACKAGE_NAME.GRANT_RESULTS")!!.size
+        )
+
+        assertEquals(
             permissionAndExpectedGrantResults.toList(),
             result.resultData!!.getStringArrayExtra("$APP_PACKAGE_NAME.PERMISSIONS")!!
                 .zip(
diff --git a/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt
new file mode 100644
index 0000000..1d8ed8e
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.app.Activity
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class NoPermissionTest : BaseUsePermissionTest() {
+    @Test
+    fun testStartActivity22() {
+        installPackage(APP_APK_PATH_22_NONE)
+
+        startAppActivityAndAssertResultCode(Activity.RESULT_OK) {}
+
+        clearTargetSdkWarning()
+    }
+
+    @Test
+    fun testStartActivityLatest() {
+        installPackage(APP_APK_PATH_LATEST_NONE)
+
+        startAppActivityAndAssertResultCode(Activity.RESULT_OK) {}
+    }
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionGroupTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionGroupTest.kt
index ee8a42f..36cdb05 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionGroupTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionGroupTest.kt
@@ -41,8 +41,8 @@
     }
 
     @Test
-    fun testRuntimeGroupGrantExpansionLatest() {
-        installPackage(APP_APK_PATH_LATEST)
+    fun testRuntimeGroupGrantExpansion30() {
+        installPackage(APP_APK_PATH_30)
         testRuntimeGroupGrantExpansion(false)
     }
 
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
index f0b1f80..6b8678d 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
@@ -28,6 +28,7 @@
 import androidx.test.runner.AndroidJUnit4
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNull
+import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -36,6 +37,12 @@
 
 @RunWith(AndroidJUnit4::class)
 class PermissionReviewTest : BaseUsePermissionTest() {
+
+    @Before
+    fun assumeNotIndividuallyControlled() {
+        Assume.assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+    }
+
     @Before
     fun installApp22CalendarOnly() {
         installPackage(APP_APK_PATH_22_CALENDAR_ONLY)
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
index 54a0e36..bb16f45 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
@@ -16,6 +16,9 @@
 
 package android.permission3.cts
 
+import android.Manifest
+import android.content.pm.PackageManager
+import com.android.compatibility.common.util.SystemUtil
 import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.Test
@@ -42,17 +45,35 @@
     }
 
     @Test
+    fun testPermissionNotSplit30() {
+        installPackage(APP_APK_PATH_30)
+        testLocationPermissionSplit(false)
+    }
+
+    @Test
     fun testPermissionNotSplitLatest() {
         installPackage(APP_APK_PATH_LATEST)
         testLocationPermissionSplit(false)
     }
 
+    @Test
+    fun testMicCamPermissionSplit30() {
+        installPackage(APP_APK_PATH_30)
+        testMicCamPermissionSplit(expectSplit = true, exemptPermissions = true)
+    }
+
+    @Test
+    fun testMicCamPermissionNoExemptNoSplit30() {
+        installPackage(APP_APK_PATH_30)
+        testMicCamPermissionSplit(expectSplit = false, exemptPermissions = false)
+    }
+
     private fun testLocationPermissionSplit(expectSplit: Boolean) {
         assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, false)
         assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
 
         requestAppPermissionsAndAssertResult(
-            android.Manifest.permission.ACCESS_FINE_LOCATION to true
+                android.Manifest.permission.ACCESS_FINE_LOCATION to true
         ) {
             if (expectSplit) {
                 clickPermissionRequestSettingsLinkAndAllowAlways()
@@ -63,4 +84,43 @@
 
         assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, expectSplit)
     }
+
+    private fun testMicCamPermissionSplit(expectSplit: Boolean, exemptPermissions: Boolean) {
+        assertAppHasPermission(android.Manifest.permission.RECORD_AUDIO, false)
+        assertAppHasPermission(android.Manifest.permission.CAMERA, false)
+
+        if (exemptPermissions) {
+            SystemUtil.runWithShellPermissionIdentity {
+                packageManager.addWhitelistedRestrictedPermission(APP_PACKAGE_NAME,
+                        Manifest.permission.RECORD_BACKGROUND_AUDIO,
+                        PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)
+                packageManager.addWhitelistedRestrictedPermission(APP_PACKAGE_NAME,
+                        Manifest.permission.BACKGROUND_CAMERA,
+                        PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)
+            }
+        }
+
+        requestAppPermissionsAndAssertResult(
+                android.Manifest.permission.RECORD_AUDIO to true
+        ) {
+            if (expectSplit) {
+                clickPermissionRequestSettingsLinkAndAllowAlways()
+            } else {
+                clickPermissionRequestAllowForegroundButton()
+            }
+        }
+        assertAppHasPermission(android.Manifest.permission.RECORD_BACKGROUND_AUDIO, expectSplit)
+
+        requestAppPermissionsAndAssertResult(
+                android.Manifest.permission.CAMERA to true
+        ) {
+            if (expectSplit) {
+                clickPermissionRequestSettingsLinkAndAllowAlways()
+            } else {
+                clickPermissionRequestAllowForegroundButton()
+            }
+        }
+
+        assertAppHasPermission(android.Manifest.permission.BACKGROUND_CAMERA, expectSplit)
+    }
 }
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest22.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest22.kt
index 7d06770..d6cc872 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest22.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest22.kt
@@ -16,6 +16,7 @@
 
 package android.permission3.cts
 
+import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
 
@@ -23,6 +24,12 @@
  * Runtime permission behavior tests for apps targeting API 22.
  */
 class PermissionTest22 : BaseUsePermissionTest() {
+
+    @Before
+    fun assumeNotIndividuallyControlled() {
+        Assume.assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+    }
+
     @Before
     fun installApp22AndApprovePermissionReview() {
         installPackage(APP_APK_PATH_22)
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
index 59cb9aa..31e6f3a 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
@@ -17,6 +17,7 @@
 package android.permission3.cts
 
 import androidx.test.filters.FlakyTest
+import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
 
@@ -232,6 +233,8 @@
     @Test(timeout = 120000)
     @FlakyTest
     fun testNoResidualPermissionsOnUninstall() {
+        Assume.assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+
         // Grant all permissions
         grantAppPermissions(
             android.Manifest.permission.WRITE_CALENDAR,
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt
index 1272355..614cdc6 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt
@@ -27,7 +27,7 @@
 
     @Test
     fun testCantRequestFgAndBgAtOnce() {
-        installPackage(APP_APK_PATH_LATEST_WITH_BACKGROUND)
+        installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
         assertAppHasPermission(ACCESS_FINE_LOCATION, false)
         assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
 
@@ -39,7 +39,7 @@
 
     @Test
     fun testRequestBothInSequence() {
-        installPackage(APP_APK_PATH_LATEST_WITH_BACKGROUND)
+        installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
         assertAppHasPermission(ACCESS_FINE_LOCATION, false)
         assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
 
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt
new file mode 100644
index 0000000..40714b3
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.os.Build
+import org.junit.Assert
+import org.junit.Test
+
+/**
+ * Runtime permission behavior apps targeting API S
+ * STOPSHIP Rename once api number is finalized
+ */
+class PermissionTestLatest : BaseUsePermissionTest() {
+
+    /**
+     * Not exactly a cts type test but it needs to be run continuously. This test is supposed to
+     * start failing once the sdk integer is decided for S. This is important to have because it was
+     * assumed that the sdk int will be 31 in frameworks/base/data/etc/platform.xml. This test
+     * should be removed once the sdk is finalized.
+     */
+    @Test
+    fun testSApiVersionCodeIsNotSet() {
+        Assert.assertEquals(Build.VERSION_CODES.R, Build.VERSION.SDK_INT)
+    }
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionUpgradeTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionUpgradeTest.kt
index fcfc1bb..de58d43 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionUpgradeTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionUpgradeTest.kt
@@ -16,6 +16,7 @@
 
 package android.permission3.cts
 
+import org.junit.Assume
 import org.junit.Test
 
 /**
@@ -25,6 +26,8 @@
 
     @Test
     fun testUpgradeKeepsPermissions() {
+        Assume.assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+
         installPackage(APP_APK_PATH_22)
 
         approvePermissionReview()
@@ -80,6 +83,8 @@
 
     @Test
     fun testRevokePropagatedOnUpgradeOldToNewModel() {
+        Assume.assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+
         installPackage(APP_APK_PATH_22)
 
         approvePermissionReview()
diff --git a/tests/tests/permission4/Android.bp b/tests/tests/permission4/Android.bp
index ad1658d..3ba2d32 100644
--- a/tests/tests/permission4/Android.bp
+++ b/tests/tests/permission4/Android.bp
@@ -27,6 +27,7 @@
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
+        "cts-wm-util",
     ],
     test_suites: [
         "cts",
diff --git a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
index 0f36f5a..8a4ed43 100644
--- a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
@@ -26,6 +26,7 @@
 import android.os.Process
 import android.provider.DeviceConfig
 import android.provider.Settings
+import android.server.wm.WindowManagerStateHelper
 import android.support.test.uiautomator.By
 import android.support.test.uiautomator.UiDevice
 import android.support.test.uiautomator.UiSelector
@@ -51,6 +52,7 @@
 private const val IDLE_TIMEOUT_MILLIS: Long = 1000
 private const val UNEXPECTED_TIMEOUT_MILLIS = 1000
 private const val TIMEOUT_MILLIS: Long = 20000
+private const val TV_MIC_INDICATOR_WINDOW_TITLE = "MicrophoneCaptureIndicator"
 
 class CameraMicIndicatorsPermissionTest {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
@@ -144,7 +146,30 @@
             val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
             assertTrue("View with text $APP_LABEL not found", appView.exists())
         }
-        uiDevice.openNotification()
+
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+            assertTvIndicatorsShown(useMic, useCamera)
+        } else {
+            uiDevice.openNotification()
+            assertPrivacyChipAndIndicatorsPresent(useMic, useCamera)
+        }
+
+        pressBack()
+    }
+
+    private fun assertTvIndicatorsShown(useMic: Boolean, useCamera: Boolean) {
+        if (useMic) {
+            WindowManagerStateHelper().waitFor("Waiting for the mic indicator window to come up") {
+                it.containsWindow(TV_MIC_INDICATOR_WINDOW_TITLE) &&
+                        it.isWindowVisible(TV_MIC_INDICATOR_WINDOW_TITLE)
+            }
+        }
+        if (useCamera) {
+            // There is no camera indicator on TVs.
+        }
+    }
+
+    private fun assertPrivacyChipAndIndicatorsPresent(useMic: Boolean, useCamera: Boolean) {
         // Ensure the privacy chip is present
         eventually {
             val privacyChip = uiDevice.findObject(UiSelector().resourceId(PRIVACY_CHIP_ID))
@@ -163,7 +188,6 @@
             val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
             assertTrue("View with text $APP_LABEL not found", appView.exists())
         }
-        pressBack()
     }
 
     private fun pressBack() {
diff --git a/tests/tests/print/AndroidManifest.xml b/tests/tests/print/AndroidManifest.xml
index b3c3f59..746f970 100644
--- a/tests/tests/print/AndroidManifest.xml
+++ b/tests/tests/print/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
     Copyright (C) 2014 The Android Open Source Project
 
@@ -17,72 +16,66 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.print.cts" android:targetSandboxVersion="2">
+     package="android.print.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
-    <application android:allowBackup="false" >
+    <application android:allowBackup="false">
 
         <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name="android.print.test.PrintDocumentActivity"
-            android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
-            android:theme="@style/NoAnimation" />
+        <activity android:name="android.print.test.PrintDocumentActivity"
+             android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
+             android:theme="@style/NoAnimation"/>
 
-        <service
-            android:name="android.print.test.services.FirstPrintService"
-            android:permission="android.permission.BIND_PRINT_SERVICE">
+        <service android:name="android.print.test.services.FirstPrintService"
+             android:permission="android.permission.BIND_PRINT_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.printservice.PrintService" />
+                <action android:name="android.printservice.PrintService"/>
             </intent-filter>
-            <meta-data
-               android:name="android.printservice"
-               android:resource="@xml/printservice">
+            <meta-data android:name="android.printservice"
+                 android:resource="@xml/printservice">
             </meta-data>
         </service>
 
-        <service
-            android:name="android.print.test.services.SecondPrintService"
-            android:permission="android.permission.BIND_PRINT_SERVICE">
+        <service android:name="android.print.test.services.SecondPrintService"
+             android:permission="android.permission.BIND_PRINT_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.printservice.PrintService" />
+                <action android:name="android.printservice.PrintService"/>
             </intent-filter>
-            <meta-data
-               android:name="android.printservice"
-               android:resource="@xml/printservice">
+            <meta-data android:name="android.printservice"
+                 android:resource="@xml/printservice">
             </meta-data>
         </service>
 
-        <activity
-            android:name="android.print.test.services.SettingsActivity"
-            android:theme="@style/NoAnimation"
-            android:exported="true">
+        <activity android:name="android.print.test.services.SettingsActivity"
+             android:theme="@style/NoAnimation"
+             android:exported="true">
         </activity>
 
-        <activity
-            android:name="android.print.test.services.AddPrintersActivity"
-            android:theme="@style/NoAnimation"
-            android:exported="true">
+        <activity android:name="android.print.test.services.AddPrintersActivity"
+             android:theme="@style/NoAnimation"
+             android:exported="true">
         </activity>
 
-        <activity
-            android:name="android.print.test.services.InfoActivity"
-            android:theme="@style/NoAnimation"
-            android:exported="true">
+        <activity android:name="android.print.test.services.InfoActivity"
+             android:theme="@style/NoAnimation"
+             android:exported="true">
         </activity>
 
-        <activity
-            android:name="android.print.test.services.CustomPrintOptionsActivity"
-            android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
-            android:exported="true"
-            android:theme="@style/NoAnimationTranslucent">
+        <activity android:name="android.print.test.services.CustomPrintOptionsActivity"
+             android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
+             android:exported="true"
+             android:theme="@style/NoAnimationTranslucent">
         </activity>
 
   </application>
 
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-          android:targetPackage="android.print.cts"
-          android:label="Tests for the print APIs."/>
+       android:targetPackage="android.print.cts"
+       android:label="Tests for the print APIs."/>
 
 </manifest>
diff --git a/tests/tests/print/ExternalPrintService/AndroidManifest.xml b/tests/tests/print/ExternalPrintService/AndroidManifest.xml
index ff480c1..15100f0 100644
--- a/tests/tests/print/ExternalPrintService/AndroidManifest.xml
+++ b/tests/tests/print/ExternalPrintService/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   Copyright (C) 2018 The Android Open Source Project
 
@@ -15,19 +16,18 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.print.cts.externalservice" >
+     package="android.print.cts.externalservice">
 
-    <application android:allowBackup="false" >
-        <service
-            android:name=".ExternalService"
-            android:permission="android.permission.BIND_PRINT_SERVICE">
+    <application android:allowBackup="false">
+        <service android:name=".ExternalService"
+             android:permission="android.permission.BIND_PRINT_SERVICE"
+             android:exported="true">
           <intent-filter>
-              <action android:name="android.printservice.PrintService" />
+              <action android:name="android.printservice.PrintService"/>
           </intent-filter>
 
-          <meta-data
-              android:name="android.printservice"
-              android:resource="@xml/printservice">
+          <meta-data android:name="android.printservice"
+               android:resource="@xml/printservice">
           </meta-data>
         </service>
     </application>
diff --git a/tests/tests/print/TEST_MAPPING b/tests/tests/print/TEST_MAPPING
new file mode 100644
index 0000000..325edcd
--- /dev/null
+++ b/tests/tests/print/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPrintTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/provider/AndroidManifest.xml b/tests/tests/provider/AndroidManifest.xml
index 84d2528..7139333 100644
--- a/tests/tests/provider/AndroidManifest.xml
+++ b/tests/tests/provider/AndroidManifest.xml
@@ -16,119 +16,121 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.provider.cts">
+     package="android.provider.cts">
 
-    <uses-sdk android:targetSdkVersion="28" />
+    <uses-sdk android:targetSdkVersion="28"/>
 
     <!-- This is required for android.provider.cts.media.MediaStore_MetadataKeysTest
-    when upgrading targetSdkVersion to R+
+            when upgrading targetSdkVersion to R+
     <queries>
         <intent>
-            <action android:name="android.provider.action.REVIEW" />
+            <action android:name="android.provider.action.REVIEW"
+                 />
         </intent>
     </queries> -->
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
-    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
-    <uses-permission android:name="com.android.voicemail.permission.WRITE_VOICEMAIL" />
-    <uses-permission android:name="com.android.voicemail.permission.READ_VOICEMAIL" />
-    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.READ_CALL_LOG" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"/>
+    <uses-permission android:name="com.android.voicemail.permission.WRITE_VOICEMAIL"/>
+    <uses-permission android:name="com.android.voicemail.permission.READ_VOICEMAIL"/>
+    <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
 
     <application>
         <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.provider.cts.BrowserStubActivity"
-            android:label="BrowserStubActivity">
+             android:label="BrowserStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <service android:name=".MockInputMethodService"
-                 android:label="UserDictionaryInputMethodTestService"
-                 android:permission="android.permission.BIND_INPUT_METHOD">
+             android:label="UserDictionaryInputMethodTestService"
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
             <meta-data android:name="android.view.im"
-                       android:resource="@xml/method" />
+                 android:resource="@xml/method"/>
         </service>
 
         <provider android:name="android.provider.cts.MockFontProvider"
-                  android:authorities="android.provider.fonts.cts.font"
-                  android:exported="false"
-                  android:multiprocess="true" />
+             android:authorities="android.provider.fonts.cts.font"
+             android:exported="false"
+             android:multiprocess="true"/>
 
         <provider android:name="android.provider.cts.TestSRSProvider"
-                  android:authorities="android.provider.cts.TestSRSProvider"
-                  android:exported="false" />
+             android:authorities="android.provider.cts.TestSRSProvider"
+             android:exported="false"/>
 
-        <service
-            android:name="android.provider.cts.contacts.StubInCallService"
-            android:permission="android.permission.BIND_INCALL_SERVICE">
+        <service android:name="android.provider.cts.contacts.StubInCallService"
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
-            <meta-data
-                android:name="android.telecom.IN_CALL_SERVICE_UI"
-                android:value="true"/>
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI"
+                 android:value="true"/>
         </service>
 
-        <activity android:name="android.provider.cts.contacts.StubDialerActivity">
+        <activity android:name="android.provider.cts.contacts.StubDialerActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.provider.cts"
-                     android:label="CTS tests of android.provider">
+         android:targetPackage="android.provider.cts"
+         android:label="CTS tests of android.provider">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/provider/app/GalleryTestApp/Android.bp b/tests/tests/provider/app/GalleryTestApp/Android.bp
index 9f2359c..5da74ce 100644
--- a/tests/tests/provider/app/GalleryTestApp/Android.bp
+++ b/tests/tests/provider/app/GalleryTestApp/Android.bp
@@ -25,4 +25,5 @@
     optimize: {
         enabled: false,
     },
+    min_sdk_version : "29",
 }
diff --git a/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml b/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml
index 24eb5e0..13b423d 100644
--- a/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml
+++ b/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml
@@ -15,18 +15,19 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.provider.apps.cts.gallerytestapp">
+     package="android.provider.apps.cts.gallerytestapp">
 
     <application>
         <service android:name=".ReviewPrewarmService"/>
-        <activity android:name=".ReviewActivity">
+        <activity android:name=".ReviewActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.action.REVIEW" />
-                <data android:scheme="content" />
+                <action android:name="android.provider.action.REVIEW"/>
+                <data android:scheme="content"/>
             </intent-filter>
             <meta-data android:name="android.media.review_gallery_prewarm_service"
-                       android:value="android.provider.apps.cts.gallerytestapp.ReviewPrewarmService"/>
+                 android:value="android.provider.apps.cts.gallerytestapp.ReviewPrewarmService"/>
         </activity>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
index 942d4f4..99b8ef2 100644
--- a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
@@ -397,7 +397,11 @@
 
     public static void assertExists(String msg, String path) throws IOException {
         if (!access(path)) {
-            fail(msg);
+            if (msg != null) {
+                fail(path + ": " + msg);
+            } else {
+                fail("File " + path + " does not exist");
+            }
         }
     }
 
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
index 0dae752..cb97034 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
@@ -79,7 +79,7 @@
         mContext = InstrumentationRegistry.getTargetContext();
         mContentResolver = mContext.getContentResolver();
 
-        Log.d(TAG, "Using volume " + mVolumeName);
+        Log.d(TAG, "Using volume " + mVolumeName + " for user " + mContext.getUserId());
         mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
     }
 
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java
index a3bd099..12e3703 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java
@@ -100,6 +100,7 @@
             assertEquals(1, c.getCount());
             c.moveToFirst();
 
+            assertFalse(c.isNull(c.getColumnIndex(Albums._ID)));
             assertFalse(c.isNull(c.getColumnIndex(Albums.ALBUM_ID)));
             assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Albums.ALBUM)));
             assertNull(c.getString(c.getColumnIndex(Albums.ALBUM_ART)));
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java
index 479c3ab..4dbed95 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java
@@ -113,7 +113,7 @@
 
         mRowsAdded = new ArrayList<Uri>();
 
-        Log.d(TAG, "Using volume " + mVolumeName);
+        Log.d(TAG, "Using volume " + mVolumeName + " for user " + mContext.getUserId());
         mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
 
         final Resources res = mContext.getResources();
diff --git a/tests/tests/renderscript/TEST_MAPPING b/tests/tests/renderscript/TEST_MAPPING
new file mode 100644
index 0000000..b49b9d0
--- /dev/null
+++ b/tests/tests/renderscript/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsRenderscriptTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/renderscriptlegacy/TEST_MAPPING b/tests/tests/renderscriptlegacy/TEST_MAPPING
new file mode 100644
index 0000000..b5083ba
--- /dev/null
+++ b/tests/tests/renderscriptlegacy/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsRenderscriptLegacyTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/resolverservice/TEST_MAPPING b/tests/tests/resolverservice/TEST_MAPPING
new file mode 100644
index 0000000..bab3685
--- /dev/null
+++ b/tests/tests/resolverservice/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsResolverServiceTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/resourcesloader/TEST_MAPPING b/tests/tests/resourcesloader/TEST_MAPPING
new file mode 100644
index 0000000..9ebc996
--- /dev/null
+++ b/tests/tests/resourcesloader/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsResourcesLoaderTests"
+    }
+  ]
+}
diff --git a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
index 742db92..eb17122 100644
--- a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
+++ b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
@@ -17,8 +17,8 @@
   -->
 
 <manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.app.role.cts.app">
+     xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.app.role.cts.app">
 
     <uses-permission android:name="android.permission.SEND_SMS" />
 
@@ -41,20 +41,24 @@
             android:exported="true" />
 
         <!-- Dialer -->
-        <activity android:name=".DialerDialActivity">
+        <activity
+            android:name=".DialerDialActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.DEFAULT" />
                 <data android:scheme="tel" />
             </intent-filter>
         </activity>
 
         <!-- Sms -->
-        <activity android:name=".SmsSendToActivity">
+        <activity
+            android:name=".SmsSendToActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SENDTO" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -63,7 +67,8 @@
         </activity>
         <service
             android:name=".SmsRespondViaMessageService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE">
+            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -72,14 +77,16 @@
         </service>
         <receiver
             android:name=".SmsDelieverReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+            android:permission="android.permission.BROADCAST_SMS"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.provider.Telephony.SMS_DELIVER" />
             </intent-filter>
         </receiver>
         <receiver
             android:name=".SmsWapPushDelieverReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+            android:permission="android.permission.BROADCAST_WAP_PUSH"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
                 <data android:mimeType="application/vnd.wap.mms-message" />
@@ -87,7 +94,9 @@
         </receiver>
 
         <!-- Browser -->
-        <activity android:name=".BrowserActivity">
+        <activity
+            android:name=".BrowserActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.BROWSABLE" />
@@ -97,7 +106,9 @@
         </activity>
 
         <!-- Assistant -->
-        <activity android:name=".AssistantActivity">
+        <activity
+            android:name=".AssistantActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.ASSIST" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/tests/tests/role/CtsRoleTestApp28/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp28/AndroidManifest.xml
index 032d94d..f3ba473 100644
--- a/tests/tests/role/CtsRoleTestApp28/AndroidManifest.xml
+++ b/tests/tests/role/CtsRoleTestApp28/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2019 The Android Open Source Project
   ~
@@ -16,67 +15,67 @@
   ~ limitations under the License.
   -->
 
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.app.role.cts.app28">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.app.role.cts.app28">
 
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
+    <uses-sdk android:minSdkVersion="28"
+         android:targetSdkVersion="28"/>
 
-    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS"/>
 
     <application android:label="CtsRoleTestApp28">
 
-        <activity
-            android:name=".ChangeDefaultDialerActivity"
-            android:exported="true" />
+        <activity android:name=".ChangeDefaultDialerActivity"
+             android:exported="true"/>
 
-        <activity
-            android:name=".ChangeDefaultSmsActivity"
-            android:exported="true" />
+        <activity android:name=".ChangeDefaultSmsActivity"
+             android:exported="true"/>
 
         <!-- Dialer -->
-        <activity android:name=".DialerDialActivity">
+        <activity android:name=".DialerDialActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
+                <action android:name="android.intent.action.DIAL"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
+                <action android:name="android.intent.action.DIAL"/>
                 <category android:name="android.intent.category.DEFAULT"/>
-                <data android:scheme="tel" />
+                <data android:scheme="tel"/>
             </intent-filter>
         </activity>
 
         <!-- Sms -->
-        <activity android:name=".SmsSendToActivity">
+        <activity android:name=".SmsSendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="smsto" />
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="smsto"/>
             </intent-filter>
         </activity>
-        <service
-            android:name=".SmsRespondViaMessageService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE">
+        <service android:name=".SmsRespondViaMessageService"
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="smsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="smsto"/>
             </intent-filter>
         </service>
-        <receiver
-            android:name=".SmsDelieverReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+        <receiver android:name=".SmsDelieverReceiver"
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
-        <receiver
-            android:name=".SmsWapPushDelieverReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+        <receiver android:name=".SmsWapPushDelieverReceiver"
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
index 00ddc34..47a580c 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
@@ -930,6 +930,16 @@
                 APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_GRANTED);
     }
 
+    @Test
+    public void smsRoleHolderAvailableWithoutObserveRoleHolders() throws Exception {
+        assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+        addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+
+        assertThat(sRoleManager.getSmsRoleHolder(Process.myUserHandle().getIdentifier()))
+                .isEqualTo(APP_PACKAGE_NAME);
+    }
+
     private List<String> getRoleHolders(@NonNull String roleName) throws Exception {
         return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName));
     }
diff --git a/tests/tests/rsblas/TEST_MAPPING b/tests/tests/rsblas/TEST_MAPPING
new file mode 100644
index 0000000..5772971
--- /dev/null
+++ b/tests/tests/rsblas/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsRsBlasTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/rscpp/TEST_MAPPING b/tests/tests/rscpp/TEST_MAPPING
new file mode 100644
index 0000000..469d1b7
--- /dev/null
+++ b/tests/tests/rscpp/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsRsCppTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/sax/TEST_MAPPING b/tests/tests/sax/TEST_MAPPING
new file mode 100644
index 0000000..f8c149d
--- /dev/null
+++ b/tests/tests/sax/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSaxTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/secure_element/omapi/TEST_MAPPING b/tests/tests/secure_element/omapi/TEST_MAPPING
new file mode 100644
index 0000000..d3a6ad9
--- /dev/null
+++ b/tests/tests/secure_element/omapi/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsOmapiTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index ad23b07..340fb90 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -16,77 +16,76 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.security.cts">
+     package="android.security.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
-    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
     <!-- For FileIntegrityManager -->
-    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
 
     <application android:usesCleartextTraffic="true">
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
 
         <service android:name="android.security.cts.SeccompDeathTestService"
-                 android:process=":death_test_service"
-                 android:isolatedProcess="true"
-                 android:exported="true"/>
+             android:process=":death_test_service"
+             android:isolatedProcess="true"
+             android:exported="true"/>
 
         <service android:name="android.security.cts.IsolatedService"
-                 android:process=":Isolated"
-                 android:isolatedProcess="true"/>
+             android:process=":Isolated"
+             android:isolatedProcess="true"/>
 
         <service android:name="android.security.cts.activity.SecureRandomService"
-                 android:process=":secureRandom"/>
-        <activity
-            android:name="android.security.cts.MotionEventTestActivity"
-            android:label="Test MotionEvent">
+             android:process=":secureRandom"/>
+        <activity android:name="android.security.cts.MotionEventTestActivity"
+             android:label="Test MotionEvent"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="android.security.cts.BinderExploitTest$CVE_2019_2213_Activity"
-            android:label="Test Binder Exploit Race Condition activity">
+        <activity android:name="android.security.cts.BinderExploitTest$CVE_2019_2213_Activity"
+             android:label="Test Binder Exploit Race Condition activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="android.security.cts.NanoAppBundleTest$FailActivity"
-            android:label="Test Nano AppBundle customized failure catch activity">
+        <activity android:name="android.security.cts.NanoAppBundleTest$FailActivity"
+             android:label="Test Nano AppBundle customized failure catch activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RUN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.RUN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name="android.security.cts.NanoAppBundleTest$AuthenticatorService"
-            android:enabled="true"
-            android:exported="true">
+        <service android:name="android.security.cts.NanoAppBundleTest$AuthenticatorService"
+             android:enabled="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/authenticator" />
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/authenticator"/>
         </service>
 
-        <activity
-            android:name="android.security.cts.SkiaJpegDecodingActivity"
-            android:label="Test overflow in libskia JPG processing">
+        <activity android:name="android.security.cts.SkiaJpegDecodingActivity"
+             android:label="Test overflow in libskia JPG processing"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
@@ -94,28 +93,28 @@
         </activity>
 
         <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
+             android:exported="true"/>
         <receiver android:name="android.security.cts.PackageVerificationsBroadcastReceiver"
-                  android:permission="android.permission.BIND_PACKAGE_VERIFIER" >
+             android:permission="android.permission.BIND_PACKAGE_VERIFIER"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
-                <data android:mimeType="application/vnd.android.package-archive" />
+                <data android:mimeType="application/vnd.android.package-archive"/>
             </intent-filter>
         </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.security.cts"
-                     android:label="CTS tests of android.security.cts">
+         android:targetPackage="android.security.cts"
+         android:label="CTS tests of android.security.cts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.security.cts"
-                     android:label="CTS tests of android.security.cts">
+         android:targetPackage="android.security.cts"
+         android:label="CTS tests of android.security.cts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CrashParserRunListener" />
+             android:value="com.android.cts.runner.CrashParserRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index b84ee15..f8df05c 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -2280,11 +2280,6 @@
                 } catch (Exception e) {
                     // local exceptions ignored, not security issues
                 } finally {
-                    try {
-                        codec.stop();
-                    } catch (Exception e) {
-                        // local exceptions ignored, not security issues
-                    }
                     codec.release();
                     renderTarget.destroy();
                 }
diff --git a/tests/tests/security/testdata/packageinstallertestapp.xml b/tests/tests/security/testdata/packageinstallertestapp.xml
index 7c35c11..5e6e066 100644
--- a/tests/tests/security/testdata/packageinstallertestapp.xml
+++ b/tests/tests/security/testdata/packageinstallertestapp.xml
@@ -16,21 +16,22 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.security.cts.packageinstallertestapp"
-          android:versionCode="1"
-          android:versionName="1.0" >
+     package="android.security.cts.packageinstallertestapp"
+     android:versionCode="1"
+     android:versionName="1.0">
 
 
     <package-verifier android:name="android.security.cts"
-                      android:publicKey="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rB8dYLa9mhYe9GICodUFVdjzh00SsfzpdMZ4UGIGF6VY/7D/TCdT5vjdXOdOQtsQnM/nZSgUPgBVX8RObm4/PRix68rdl2J58/LstcqdG6EaExb5hPUzHUuvOfd+p+IP+0SFEuRrWeGsmkzvdnxC2ZZjzEpE8UNDS8EtC2qULkF0cAGcHdHsjlktXRvn4FO+RN1GW6yxs8mOyCabNHASe3AynYFa894Iamu99+RK51+3iyw+u4cVUeVPH3CzJ2Pu1PyqT+9l4gKUbw0gfC6D0/PNEfxe4RPrtn3Z8+ES8+jXPjBLLaMTpT9dFcP25kBwNLiV0MJdTOdZ3f30urtJQIDAQAB" />
+         android:publicKey="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rB8dYLa9mhYe9GICodUFVdjzh00SsfzpdMZ4UGIGF6VY/7D/TCdT5vjdXOdOQtsQnM/nZSgUPgBVX8RObm4/PRix68rdl2J58/LstcqdG6EaExb5hPUzHUuvOfd+p+IP+0SFEuRrWeGsmkzvdnxC2ZZjzEpE8UNDS8EtC2qULkF0cAGcHdHsjlktXRvn4FO+RN1GW6yxs8mOyCabNHASe3AynYFa894Iamu99+RK51+3iyw+u4cVUeVPH3CzJ2Pu1PyqT+9l4gKUbw0gfC6D0/PNEfxe4RPrtn3Z8+ES8+jXPjBLLaMTpT9dFcP25kBwNLiV0MJdTOdZ3f30urtJQIDAQAB"/>
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="PackageInstallerTest Test App">
-        <activity android:name="android.security.cts.packageinstallertestapp.MainActivity">
+        <activity android:name="android.security.cts.packageinstallertestapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/selinux/selinuxTargetSdk29/TEST_MAPPING b/tests/tests/selinux/selinuxTargetSdk29/TEST_MAPPING
new file mode 100644
index 0000000..fa4a8c1
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk29/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSelinuxTargetSdk29TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/sharesheet/AndroidManifest.xml b/tests/tests/sharesheet/AndroidManifest.xml
index 5723d51..ae11600 100644
--- a/tests/tests/sharesheet/AndroidManifest.xml
+++ b/tests/tests/sharesheet/AndroidManifest.xml
@@ -16,65 +16,65 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sharesheet.cts">
+     package="android.sharesheet.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
     <!-- Allows test to query for all installed apps, needed to test excluding components -->
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
     <!-- Needed permission and android:requestLegacyExternalStorage to dump screenshots in case of
-         failure -->
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+                 failure -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
 
-    <application 
-        android:requestLegacyExternalStorage="true"
-        android:label="@string/test_app_label">
+    <application android:requestLegacyExternalStorage="true"
+         android:label="@string/test_app_label">
 
-        <uses-library android:name="android.test.runner" />
-    
-        <activity android:name=".CtsSharesheetDeviceActivity">
+        <uses-library android:name="android.test.runner"/>
+
+        <activity android:name=".CtsSharesheetDeviceActivity"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
 
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="test/cts" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="test/cts"/>
             </intent-filter>
 
             <!-- Used to provide Sharing Shortcuts -->
             <meta-data android:name="android.app.shortcuts"
-                    android:resource="@xml/shortcuts"/>
+                 android:resource="@xml/shortcuts"/>
 
             <meta-data android:name="android.service.chooser.chooser_target_service"
-                    android:value=".CtsSharesheetChooserTargetService"/>
+                 android:value=".CtsSharesheetChooserTargetService"/>
 
         </activity>
 
         <service android:name=".CtsSharesheetChooserTargetService"
-            android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
+             android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.chooser.ChooserTargetService" />
+                <action android:name="android.service.chooser.ChooserTargetService"/>
             </intent-filter>
         </service>
 
         <activity-alias android:name=".ExtraInitialIntentTestActivity"
-                        android:label="@string/test_extra_initial_intents_label"
-                        android:targetActivity=".CtsSharesheetDeviceActivity"/>
+             android:label="@string/test_extra_initial_intents_label"
+             android:targetActivity=".CtsSharesheetDeviceActivity"/>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.sharesheet.cts"
-                     android:label="CTS tests of android.sharesheet">
+         android:targetPackage="android.sharesheet.cts"
+         android:label="CTS tests of android.sharesheet">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/sharesheet/packages/AndroidManifest-ActivityLabelTester.xml b/tests/tests/sharesheet/packages/AndroidManifest-ActivityLabelTester.xml
index 9da4a37..635907c 100644
--- a/tests/tests/sharesheet/packages/AndroidManifest-ActivityLabelTester.xml
+++ b/tests/tests/sharesheet/packages/AndroidManifest-ActivityLabelTester.xml
@@ -16,21 +16,23 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sharesheet.cts.packages">
+     package="android.sharesheet.cts.packages">
 
     <application android:label="App A">
 
-        <activity android:name=".LabelTestActivity" android:label="Activity A">
+        <activity android:name=".LabelTestActivity"
+             android:label="Activity A"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
 
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="test/cts" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="test/cts"/>
             </intent-filter>
 
         </activity>
@@ -38,4 +40,3 @@
     </application>
 
 </manifest>
-
diff --git a/tests/tests/sharesheet/packages/AndroidManifest-ExcludeTester.xml b/tests/tests/sharesheet/packages/AndroidManifest-ExcludeTester.xml
index ca2e79e..24b8762 100644
--- a/tests/tests/sharesheet/packages/AndroidManifest-ExcludeTester.xml
+++ b/tests/tests/sharesheet/packages/AndroidManifest-ExcludeTester.xml
@@ -16,21 +16,22 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sharesheet.cts.packages">
+     package="android.sharesheet.cts.packages">
 
     <application android:label="Bl Label">
 
-        <activity android:name=".LabelTestActivity">
+        <activity android:name=".LabelTestActivity"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
 
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="test/cts" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="test/cts"/>
             </intent-filter>
 
         </activity>
@@ -38,4 +39,3 @@
     </application>
 
 </manifest>
-
diff --git a/tests/tests/sharesheet/packages/AndroidManifest-IntentFilterLabelTester.xml b/tests/tests/sharesheet/packages/AndroidManifest-IntentFilterLabelTester.xml
index 1169b49..6450034 100644
--- a/tests/tests/sharesheet/packages/AndroidManifest-IntentFilterLabelTester.xml
+++ b/tests/tests/sharesheet/packages/AndroidManifest-IntentFilterLabelTester.xml
@@ -16,21 +16,23 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sharesheet.cts.packages">
+     package="android.sharesheet.cts.packages">
 
     <application android:label="App If">
 
-        <activity android:name=".LabelTestActivity" android:label="Activity If">
+        <activity android:name=".LabelTestActivity"
+             android:label="Activity If"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
 
             <intent-filter android:label="IntentFilter If">
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="test/cts" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="test/cts"/>
             </intent-filter>
 
         </activity>
@@ -38,4 +40,3 @@
     </application>
 
 </manifest>
-
diff --git a/tests/tests/shortcutmanager/AndroidManifest.xml b/tests/tests/shortcutmanager/AndroidManifest.xml
index 29b2e83..05e6ef7 100755
--- a/tests/tests/shortcutmanager/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/AndroidManifest.xml
@@ -13,54 +13,57 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.pm.cts.shortcutmanager"
-    android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+     package="android.content.pm.cts.shortcutmanager"
+     android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="android.content.pm.cts.shortcutmanager.MyActivity" />
+        <activity android:name="android.content.pm.cts.shortcutmanager.MyActivity"/>
 
         <activity-alias android:name="non_main"
-            android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity" >
+             android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity">
         </activity-alias>
         <activity-alias android:name="disabled_main"
-            android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity"
-            android:enabled="false">
+             android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
         <activity-alias android:name="main"
-            android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity">
+             android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="main_shortcut_config"
-                        android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity">
+             android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.CREATE_SHORTCUT" />
+                <action android:name="android.intent.action.CREATE_SHORTCUT"/>
             </intent-filter>
         </activity-alias>
 
         <!-- It's not exporeted, but should still be launchable. -->
         <activity android:name="android.content.pm.cts.shortcutmanager.ShortcutLaunchedActivity"
-            android:exported="false"/>
+             android:exported="false"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.content.pm.cts.shortcutmanager"
-        android:label="CTS tests for ShortcutManager">
+         android:targetPackage="android.content.pm.cts.shortcutmanager"
+         android:label="CTS tests for ShortcutManager">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
index ec688fe..4b7dcd9 100755
--- a/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
@@ -16,40 +16,43 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.pm.cts.shortcutmanager.packages"
-    android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+     package="android.content.pm.cts.shortcutmanager.packages"
+     android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="Launcher">
+        <activity android:name="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
         <activity-alias android:name="Launcher2"
-                android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity-alias>
         <activity-alias android:name="Launcher3"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity-alias>
-        <activity android:name="ShortcutConfirmPin">
+        <activity android:name="ShortcutConfirmPin"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
+                <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT"/>
             </intent-filter>
         </activity>
     </application>
 </manifest>
-
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
index 90d0df5..72f7bd4 100755
--- a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
@@ -16,18 +16,18 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.pm.cts.shortcutmanager.packages">
+     package="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="Launcher">
+        <activity android:name="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
     </application>
 </manifest>
-
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
index a5a0060..0baaaab 100755
--- a/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
@@ -16,143 +16,166 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.pm.cts.shortcutmanager.packages"
-    android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+     package="android.content.pm.cts.shortcutmanager.packages"
+     android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="Launcher"
-            android:enabled="true">
+             android:enabled="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
 
         <activity-alias android:name="Launcher2"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher3"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher4"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher5"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher_no_main_1"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher_no_main_2"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_1"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts1"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcuts1"/>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_2"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts2"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcuts2"/>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_3"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts3"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcuts3"/>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_4a"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts4a"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcuts4a"/>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_4b"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts4b"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcuts4b"/>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_error_1"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_1"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcut_error_1"/>
         </activity-alias>
         <activity-alias android:name="Launcher_manifest_error_2"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_2"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcut_error_2"/>
         </activity-alias>
         <activity-alias android:name="Launcher_manifest_error_3"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_3"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcut_error_3"/>
         </activity-alias>
     </application>
 </manifest>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
index cd30602..65271c1 100755
--- a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
@@ -16,21 +16,23 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.pm.cts.shortcutmanager.packages">
+     package="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="Launcher" android:enabled="true" android:exported="false">
+        <activity android:name="Launcher"
+             android:enabled="true"
+             android:exported="false">
         </activity>
 
         <activity-alias android:name="HomeActivity"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
     </application>
 </manifest>
-
diff --git a/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/AndroidManifest.xml b/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/AndroidManifest.xml
index 348c7b4..6a831b6 100644
--- a/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/AndroidManifest.xml
+++ b/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/AndroidManifest.xml
@@ -15,16 +15,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.simpleperf.debuggable">
+     package="com.android.simpleperf.debuggable">
 
     <application android:debuggable="true">
-        <activity
-            android:label="simpleperf"
-            android:name="com.android.simpleperf.debuggable.MainActivity">
+        <activity android:label="simpleperf"
+             android:name="com.android.simpleperf.debuggable.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/tests/simpleperf/CtsSimpleperfProfileableApp/AndroidManifest.xml b/tests/tests/simpleperf/CtsSimpleperfProfileableApp/AndroidManifest.xml
index d87d10e..a8ff37b 100644
--- a/tests/tests/simpleperf/CtsSimpleperfProfileableApp/AndroidManifest.xml
+++ b/tests/tests/simpleperf/CtsSimpleperfProfileableApp/AndroidManifest.xml
@@ -15,16 +15,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.simpleperf.profileable">
+     package="com.android.simpleperf.profileable">
 
     <application>
-        <profileable android:shell="true" />
-        <activity
-            android:label="simpleperf"
-            android:name="com.android.simpleperf.profileable.MainActivity">
+        <profileable android:shell="true"/>
+        <activity android:label="simpleperf"
+             android:name="com.android.simpleperf.profileable.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/slice/AndroidManifest.xml b/tests/tests/slice/AndroidManifest.xml
index 6eb21eb..58d51d0 100644
--- a/tests/tests/slice/AndroidManifest.xml
+++ b/tests/tests/slice/AndroidManifest.xml
@@ -16,45 +16,46 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.slice.cts">
+     package="android.slice.cts">
 
-    <uses-permission android:name="android.permission.BIND_SLICE" />
+    <uses-permission android:name="android.permission.BIND_SLICE"/>
 
     <application android:label="Android TestCase"
-                android:icon="@drawable/size_48x48"
-                android:maxRecents="1"
-                android:multiArch="true"
-                android:supportsRtl="true"
-                android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
+         android:icon="@drawable/size_48x48"
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:supportsRtl="true"
+         android:debuggable="true">
+        <uses-library android:name="android.test.runner"/>
 
         <provider android:name=".SliceProvider"
-                  android:authorities="android.slice.cts"
-                  android:process=":slice_process" />
+             android:authorities="android.slice.cts"
+             android:process=":slice_process"/>
 
         <provider android:name=".LocalSliceProvider"
-            android:authorities="android.slice.cts.local">
+             android:authorities="android.slice.cts.local">
             <intent-filter>
-                <action android:name="android.slice.cts.action.TEST_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.app.slice.category.SLICE" />
+                <action android:name="android.slice.cts.action.TEST_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.app.slice.category.SLICE"/>
             </intent-filter>
         </provider>
 
-        <activity android:name=".Launcher">
+        <activity android:name=".Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.slice.cts"
-                     android:label="CTS tests of android.slice">
+         android:targetPackage="android.slice.cts"
+         android:label="CTS tests of android.slice">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/slice/src/android/slice/cts/SliceProviderTest.java b/tests/tests/slice/src/android/slice/cts/SliceProviderTest.java
index 664b4fd..b6b2f4c 100644
--- a/tests/tests/slice/src/android/slice/cts/SliceProviderTest.java
+++ b/tests/tests/slice/src/android/slice/cts/SliceProviderTest.java
@@ -19,7 +19,6 @@
 import android.content.pm.PackageManager;
 import androidx.test.InstrumentationRegistry;
 import android.content.Context;
-
 import android.app.slice.Slice;
 import android.app.slice.SliceSpec;
 import android.content.ContentResolver;
@@ -35,7 +34,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-
 import static org.junit.Assume.assumeFalse;
 
 @RunWith(AndroidJUnit4.class)
diff --git a/tests/tests/speech/AndroidManifest.xml b/tests/tests/speech/AndroidManifest.xml
index f3b4d0f..d19dea0 100755
--- a/tests/tests/speech/AndroidManifest.xml
+++ b/tests/tests/speech/AndroidManifest.xml
@@ -16,27 +16,27 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.speech.tts.cts">
+     package="android.speech.tts.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <service android:name="android.speech.tts.cts.StubTextToSpeechService">
+        <service android:name="android.speech.tts.cts.StubTextToSpeechService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TTS_SERVICE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.TTS_SERVICE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.speech.tts.cts"
-                     android:label="CTS tests of android.speech">
+         android:targetPackage="android.speech.tts.cts"
+         android:label="CTS tests of android.speech">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java b/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
index 4665644..e6e06d6 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
@@ -48,28 +48,42 @@
 
     public StubTextToSpeechService() {
         supportedLanguages.add(new Locale("eng"));
-        supportedCountries.add(new Locale("eng", "USA"));
-        supportedCountries.add(new Locale("eng", "GBR"));
-        GBFallbacks.add(new Locale("eng", "NZL"));
+        supportedCountries.add(new Locale("eng", "US"));
+        supportedCountries.add(new Locale("eng", "GB"));
+        GBFallbacks.add(new Locale("eng", "NZ"));
     }
 
     @Override
     protected String[] onGetLanguage() {
-        return new String[] { "eng", "USA", "" };
+        return new String[] { "eng", "US", "" };
     }
 
     @Override
     protected int onIsLanguageAvailable(String lang, String country, String variant) {
+        country = convertISO3CountryToISO2(country);
         if (supportedCountries.contains(new Locale(lang, country))) {
             return TextToSpeech.LANG_COUNTRY_AVAILABLE;
         }
         if (supportedLanguages.contains(new Locale(lang))) {
             return TextToSpeech.LANG_AVAILABLE;
         }
- 
+
         return TextToSpeech.LANG_NOT_SUPPORTED;
     }
 
+    private String convertISO3CountryToISO2(String iso3Country) {
+      switch (iso3Country.toUpperCase()) {
+        case "USA":
+          return "US";
+        case "GBR":
+          return "GB";
+        case "NZL":
+          return "NZ";
+        default:
+          return iso3Country;
+      }
+    }
+
     @Override
     protected int onLoadLanguage(String lang, String country, String variant) {
         return onIsLanguageAvailable(lang, country, variant);
diff --git a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
index addd14e..869b7e4 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
@@ -15,6 +15,8 @@
  */
 package android.speech.tts.cts;
 
+import android.media.AudioAttributes;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.Environment;
@@ -27,6 +29,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Tests for {@link android.speech.tts.TextToSpeechService} using StubTextToSpeechService.
@@ -34,6 +37,8 @@
 public class TextToSpeechServiceTest extends AndroidTestCase {
     private static final String UTTERANCE = "text to speech cts test";
     private static final String SAMPLE_FILE_NAME = "mytts.wav";
+    private static final String EARCON_UTTERANCE = "testEarcon";
+    private static final String SPEECH_UTTERANCE = "testSpeech";
 
     private TextToSpeechWrapper mTts;
 
@@ -206,6 +211,82 @@
         }
     }
 
+    public void testAddPlayEarcon() throws Exception {
+      File sampleFile = new File(getContext().getExternalFilesDir(null), SAMPLE_FILE_NAME);
+      try {
+        generateSampleAudio(sampleFile);
+
+        Uri sampleUri = Uri.fromFile(sampleFile);
+        assertEquals(getTts().addEarcon(EARCON_UTTERANCE, sampleFile), TextToSpeech.SUCCESS);
+
+        int result = getTts().playEarcon(EARCON_UTTERANCE,
+            TextToSpeech.QUEUE_FLUSH, createParamsBundle(EARCON_UTTERANCE), EARCON_UTTERANCE);
+
+        verifyAddPlay(result, mTts, EARCON_UTTERANCE);
+      } finally {
+        sampleFile.delete();
+      }
+    }
+
+    public void testAddPlaySpeech() throws Exception {
+      File sampleFile = new File(getContext().getExternalFilesDir(null), SAMPLE_FILE_NAME);
+      try {
+        generateSampleAudio(sampleFile);
+
+        Uri sampleUri = Uri.fromFile(sampleFile);
+        assertEquals(getTts().addSpeech(SPEECH_UTTERANCE, sampleFile), TextToSpeech.SUCCESS);
+
+        int result = getTts().speak(SPEECH_UTTERANCE,
+            TextToSpeech.QUEUE_FLUSH, createParamsBundle(SPEECH_UTTERANCE), SPEECH_UTTERANCE);
+
+        verifyAddPlay(result, mTts, SPEECH_UTTERANCE);
+      } finally {
+        sampleFile.delete();
+      }
+    }
+
+    public void testSetLanguage() {
+      TextToSpeech tts = getTts();
+
+      assertEquals(tts.setLanguage(null), TextToSpeech.LANG_NOT_SUPPORTED);
+      assertEquals(tts.setLanguage(new Locale("en", "US")), TextToSpeech.LANG_COUNTRY_AVAILABLE);
+      assertEquals(tts.setLanguage(new Locale("en")), TextToSpeech.LANG_AVAILABLE);
+      assertEquals(tts.setLanguage(new Locale("es", "US")), TextToSpeech.LANG_NOT_SUPPORTED);
+    }
+
+    public void testAddAudioAttributes() {
+      TextToSpeech tts = getTts();
+      AudioAttributes attr =
+          new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
+
+      assertEquals(tts.setAudioAttributes(null), TextToSpeech.ERROR);
+      assertEquals(tts.setAudioAttributes(attr), TextToSpeech.SUCCESS);
+    }
+
+    private void generateSampleAudio(File sampleFile) throws Exception {
+      assertFalse(sampleFile.exists());
+
+      ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(sampleFile,
+          ParcelFileDescriptor.MODE_WRITE_ONLY
+          | ParcelFileDescriptor.MODE_CREATE
+          | ParcelFileDescriptor.MODE_TRUNCATE);
+
+      Bundle params = createParamsBundle("mocktofile");
+
+      int result =
+          getTts().synthesizeToFile(
+              UTTERANCE, params, fileDescriptor,
+              params.getString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID));
+
+      verifySynthesisFile(result, mTts, sampleFile);
+    }
+
+    private void verifyAddPlay(int result, TextToSpeechWrapper mTts, String utterance)
+        throws Exception {
+      assertEquals(TextToSpeech.SUCCESS, result);
+      assertTrue(mTts.waitForComplete(utterance));
+    }
+
     private HashMap<String, String> createParams(String utteranceId) {
         HashMap<String, String> params = new HashMap<>();
         params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
diff --git a/tests/tests/systemui/Android.bp b/tests/tests/systemui/Android.bp
index 22ee8a3..f27fe1d 100644
--- a/tests/tests/systemui/Android.bp
+++ b/tests/tests/systemui/Android.bp
@@ -19,14 +19,28 @@
         "cts",
         "general-tests",
     ],
-    libs: ["android.test.runner"],
+
+    libs: [
+        "android.test.runner",
+    ],
+
     static_libs: [
+        "kotlin-stdlib",
+        "kotlin-test",
+        "systemui-cts-common",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
+        "CtsMockInputMethodLib",
         "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "androidx.test.uiautomator",
         "cts-wm-util",
         "ub-uiautomator",
+        "flickerlib",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     platform_apis: true,
 }
diff --git a/tests/tests/systemui/AndroidManifest.xml b/tests/tests/systemui/AndroidManifest.xml
index c7732c2..90fae8b 100644
--- a/tests/tests/systemui/AndroidManifest.xml
+++ b/tests/tests/systemui/AndroidManifest.xml
@@ -16,33 +16,46 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.systemui.cts"
-    android:targetSandboxVersion="2">
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+     package="android.systemui.cts"
+     android:targetSandboxVersion="2">
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
+    <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <!-- Required by flickerlib to dump window states -->
+    <uses-permission android:name="android.permission.DUMP" />
+
     <application android:requestLegacyExternalStorage="true">
         <activity android:name=".LightBarActivity"
-                android:theme="@android:style/Theme.Material.NoActionBar"
-                android:screenOrientation="portrait"></activity>
+             android:theme="@android:style/Theme.Material.NoActionBar"
+             android:screenOrientation="portrait"/>
         <activity android:name=".LightBarThemeActivity"
-                android:theme="@style/LightBarTheme"
-                android:screenOrientation="portrait"></activity>
+             android:theme="@style/LightBarTheme"
+             android:screenOrientation="portrait"/>
         <activity android:name=".WindowInsetsActivity"
-                android:theme="@android:style/Theme.Material"
-                android:screenOrientation="portrait">
+             android:theme="@android:style/Theme.Material"
+             android:screenOrientation="portrait"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <uses-library android:name="android.test.runner" />
+        <service android:name=".tv.PipNotificationListenerService"
+            android:exported="true"
+            android:label="PipNotificationListenerService"
+            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.systemui.cts">
+         android:targetPackage="android.systemui.cts">
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/systemui/AndroidTest.xml b/tests/tests/systemui/AndroidTest.xml
index 927905a..7f4e538 100644
--- a/tests/tests/systemui/AndroidTest.xml
+++ b/tests/tests/systemui/AndroidTest.xml
@@ -20,10 +20,16 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSystemUiTestCases.apk" />
+        <option name="test-file-name" value="PipTestApp.apk" />
+        <option name="test-file-name" value="AudioRecorderTestApp_AudioRecord.apk" />
+        <option name="test-file-name" value="AudioRecorderTestApp_MediaRecorder.apk" />
+        <option name="test-file-name" value="CtsMockInputMethod.apk" />
     </target_preparer>
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.systemui.cts" />
         <option name="runtime-hint" value="10m19s" />
diff --git a/tests/tests/systemui/AudioRecorderTestApp_AudioRecord/Android.bp b/tests/tests/systemui/AudioRecorderTestApp_AudioRecord/Android.bp
new file mode 100644
index 0000000..ec8d0b7
--- /dev/null
+++ b/tests/tests/systemui/AudioRecorderTestApp_AudioRecord/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "AudioRecorderTestApp_AudioRecord",
+    static_libs: ["AudioRecorderTestApp_Base"],
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/AndroidManifest.xml b/tests/tests/systemui/AudioRecorderTestApp_AudioRecord/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_app_audiorecord/AndroidManifest.xml
rename to tests/tests/systemui/AudioRecorderTestApp_AudioRecord/AndroidManifest.xml
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java b/tests/tests/systemui/AudioRecorderTestApp_AudioRecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_app_audiorecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java
rename to tests/tests/systemui/AudioRecorderTestApp_AudioRecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java
diff --git a/tests/tests/systemui/AudioRecorderTestApp_Base/Android.bp b/tests/tests/systemui/AudioRecorderTestApp_Base/Android.bp
new file mode 100644
index 0000000..5f6a552
--- /dev/null
+++ b/tests/tests/systemui/AudioRecorderTestApp_Base/Android.bp
@@ -0,0 +1,21 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_library {
+    name: "AudioRecorderTestApp_Base",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_base/AndroidManifest.xml b/tests/tests/systemui/AudioRecorderTestApp_Base/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_base/AndroidManifest.xml
rename to tests/tests/systemui/AudioRecorderTestApp_Base/AndroidManifest.xml
diff --git a/hostsidetests/systemui/audiorecorder_base/res/drawable-hdpi/ic_fg.png b/tests/tests/systemui/AudioRecorderTestApp_Base/res/drawable-hdpi/ic_fg.png
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_base/res/drawable-hdpi/ic_fg.png
rename to tests/tests/systemui/AudioRecorderTestApp_Base/res/drawable-hdpi/ic_fg.png
Binary files differ
diff --git a/hostsidetests/systemui/audiorecorder_base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java b/tests/tests/systemui/AudioRecorderTestApp_Base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java
rename to tests/tests/systemui/AudioRecorderTestApp_Base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java
diff --git a/tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/Android.bp b/tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/Android.bp
new file mode 100644
index 0000000..3e030cd
--- /dev/null
+++ b/tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "AudioRecorderTestApp_MediaRecorder",
+    static_libs: ["AudioRecorderTestApp_Base"],
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/AndroidManifest.xml b/tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_app_mediarecorder/AndroidManifest.xml
rename to tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/AndroidManifest.xml
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java b/tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_app_mediarecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java
rename to tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java
diff --git a/tests/tests/systemui/OWNERS b/tests/tests/systemui/OWNERS
index 22ceb12..0d94f45 100644
--- a/tests/tests/systemui/OWNERS
+++ b/tests/tests/systemui/OWNERS
@@ -1,4 +1,7 @@
 # Bug component: 181502
 evanlaird@google.com
 felkachang@google.com
-
+# Owners of the pip tests on atv:
+rgl@google.com
+sergeynv@google.com
+galinap@google.com
diff --git a/tests/tests/systemui/PipTestApp/Android.bp b/tests/tests/systemui/PipTestApp/Android.bp
new file mode 100644
index 0000000..59b5b66
--- /dev/null
+++ b/tests/tests/systemui/PipTestApp/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "PipTestApp",
+    defaults: ["cts_defaults"],
+
+    sdk_version: "test_current",
+    min_sdk_version: "26",
+
+    manifest: "AndroidManifest.xml",
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "kotlin-stdlib",
+        "systemui-cts-common",
+    ],
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/systemui/PipTestApp/AndroidManifest.xml b/tests/tests/systemui/PipTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..3f135f5
--- /dev/null
+++ b/tests/tests/systemui/PipTestApp/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.systemui.cts.tv.pip">
+
+    <application>
+        <activity
+            android:name=".PipTestActivity"
+            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+            android:exported="true"
+            android:launchMode="singleTask"
+            android:supportsPictureInPicture="true"
+            android:taskAffinity="nobody.but.PipTestActivity" />
+
+        <activity
+            android:name=".KeyboardActivity"
+            android:exported="true"
+            android:launchMode="singleInstance"
+            android:windowSoftInputMode="stateAlwaysVisible" />
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/tests/systemui/PipTestApp/res/layout/keyboard_layout.xml b/tests/tests/systemui/PipTestApp/res/layout/keyboard_layout.xml
new file mode 100644
index 0000000..163c00e
--- /dev/null
+++ b/tests/tests/systemui/PipTestApp/res/layout/keyboard_layout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ABACAB">
+
+    <EditText
+        android:id="@+id/plain_text_input"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:inputType="text">
+        <requestFocus/>
+    </EditText>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/tests/tests/systemui/PipTestApp/src/android/systemui/cts/tv/pip/KeyboardActivity.kt b/tests/tests/systemui/PipTestApp/src/android/systemui/cts/tv/pip/KeyboardActivity.kt
new file mode 100644
index 0000000..3a558be
--- /dev/null
+++ b/tests/tests/systemui/PipTestApp/src/android/systemui/cts/tv/pip/KeyboardActivity.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.systemui.cts.tv.pip
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.systemui.tv.cts.KeyboardActivity.ACTION_HIDE_KEYBOARD
+import android.systemui.tv.cts.KeyboardActivity.ACTION_SHOW_KEYBOARD
+import android.util.Log
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+
+/** A trivial activity with a single text input field. */
+class KeyboardActivity : Activity() {
+    private lateinit var inputMethodManager: InputMethodManager
+    private lateinit var textInput: EditText
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.keyboard_layout)
+
+        textInput = findViewById(R.id.plain_text_input)
+            ?: error("Could not find the plain_text_input element!")
+
+        inputMethodManager = applicationContext.getSystemService(InputMethodManager::class.java)
+            ?: error("Could not get an InputMethodManager")
+
+        handle(intent)
+    }
+
+    override fun onNewIntent(intent: Intent?) = handle(intent)
+
+    private fun handle(intent: Intent?) {
+        when (intent?.action) {
+            ACTION_SHOW_KEYBOARD -> {
+                Log.d(TAG, "Showing keyboard")
+                inputMethodManager.showSoftInput(textInput, 0)
+            }
+            ACTION_HIDE_KEYBOARD -> {
+                Log.d(TAG, "Hiding keyboard")
+                inputMethodManager.hideSoftInputFromWindow(textInput.windowToken, 0)
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "KeyboardActivity"
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/systemui/PipTestApp/src/android/systemui/cts/tv/pip/PipTestActivity.kt b/tests/tests/systemui/PipTestApp/src/android/systemui/cts/tv/pip/PipTestActivity.kt
new file mode 100644
index 0000000..729f7e0
--- /dev/null
+++ b/tests/tests/systemui/PipTestApp/src/android/systemui/cts/tv/pip/PipTestActivity.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.systemui.cts.tv.pip
+
+import android.app.Activity
+import android.app.PictureInPictureParams
+import android.app.RemoteAction
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.Rect
+import android.media.MediaMetadata
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.media.session.PlaybackState.ACTION_PAUSE
+import android.media.session.PlaybackState.ACTION_PLAY
+import android.media.session.PlaybackState.STATE_PAUSED
+import android.media.session.PlaybackState.STATE_PLAYING
+import android.media.session.PlaybackState.STATE_STOPPED
+import android.os.Bundle
+import android.systemui.tv.cts.PipActivity.ACTION_ENTER_PIP
+import android.systemui.tv.cts.PipActivity.ACTION_MEDIA_PAUSE
+import android.systemui.tv.cts.PipActivity.ACTION_MEDIA_PLAY
+import android.systemui.tv.cts.PipActivity.ACTION_SET_MEDIA_TITLE
+import android.systemui.tv.cts.PipActivity.ACTION_NO_OP
+import android.systemui.tv.cts.PipActivity.EXTRA_ASPECT_RATIO_DENOMINATOR
+import android.systemui.tv.cts.PipActivity.EXTRA_ASPECT_RATIO_NUMERATOR
+import android.systemui.tv.cts.PipActivity.EXTRA_SET_CUSTOM_ACTIONS
+import android.systemui.tv.cts.PipActivity.EXTRA_ENTER_PIP
+import android.systemui.tv.cts.PipActivity.EXTRA_MEDIA_SESSION_ACTIONS
+import android.systemui.tv.cts.PipActivity.EXTRA_MEDIA_SESSION_ACTIVE
+import android.systemui.tv.cts.PipActivity.EXTRA_MEDIA_SESSION_TITLE
+import android.systemui.tv.cts.PipActivity.EXTRA_SOURCE_RECT_HINT
+import android.systemui.tv.cts.PipActivity.EXTRA_TURN_ON_SCREEN
+import android.systemui.tv.cts.PipActivity.MEDIA_SESSION_TITLE
+import android.util.Log
+import android.util.Rational
+import java.net.URLDecoder
+
+/** A simple PiP test activity */
+class PipTestActivity : Activity() {
+    companion object {
+        private const val TAG = "PipTestActivity"
+    }
+
+    private lateinit var pipParams: PictureInPictureParams
+    private lateinit var mediaSession: MediaSession
+    private val playbackBuilder = PlaybackState.Builder()
+        .setActions(ACTION_PAUSE or ACTION_PLAY)
+        .setState(STATE_STOPPED)
+
+    private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context?, intent: Intent?) = handle(intent)
+    }
+
+    private val intentFilter = IntentFilter().apply {
+        addAction(ACTION_SET_MEDIA_TITLE)
+        addAction(ACTION_MEDIA_PLAY)
+        addAction(ACTION_MEDIA_PAUSE)
+        addAction(ACTION_NO_OP)
+    }
+
+    private val mediaCallback = object : MediaSession.Callback() {
+        override fun onPlay() =
+            mediaSession.setPlaybackState(playbackBuilder.setState(STATE_PLAYING).build())
+
+        override fun onPause() =
+            mediaSession.setPlaybackState(playbackBuilder.setState(STATE_PAUSED).build())
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        mediaSession = MediaSession(this, MEDIA_SESSION_TITLE).apply {
+            setPlaybackState(playbackBuilder.build())
+            setCallback(mediaCallback)
+        }
+        registerReceiver(broadcastReceiver, intentFilter)
+        handle(intent)
+    }
+
+    override fun onNewIntent(intent: Intent?) = handle(intent)
+
+    private fun handle(intent: Intent?) {
+        if (intent == null) {
+            return
+        }
+
+        handleScreenExtras(intent)
+
+        handleMediaExtras(intent)
+
+        handlePipExtras(intent)
+
+        when (intent.action) {
+            ACTION_NO_OP -> {
+                // explicitly do nothing
+            }
+            ACTION_MEDIA_PLAY -> {
+                Log.d(TAG, "Playing media")
+                mediaSession.controller.transportControls.play()
+            }
+            ACTION_MEDIA_PAUSE -> {
+                Log.d(TAG, "Pausing media")
+                mediaSession.controller.transportControls.pause()
+            }
+        }
+
+        if (intent.action == ACTION_ENTER_PIP || intent.getBooleanExtra(EXTRA_ENTER_PIP, false)) {
+            Log.d(TAG, "Entering PIP. Currently in PIP = $isInPictureInPictureMode")
+            val res = enterPictureInPictureMode(pipParams)
+            Log.d(TAG, "Entered PIP = $res. Currently in PIP = $isInPictureInPictureMode")
+        }
+    }
+
+    /**
+     * Applies the pip parameters from the intent to the current pip window if there is one, or
+     * sets them for when pip mode will be entered next.
+     *
+     * Also stores the new parameters in [pipParams].
+     */
+    private fun handlePipExtras(intent: Intent) {
+        pipParams = buildPipParams(intent.extras)
+        setPictureInPictureParams(pipParams)
+    }
+
+    /**  Updates the state of the [mediaSession]. */
+    private fun handleMediaExtras(intent: Intent) {
+        if (intent.hasExtra(EXTRA_MEDIA_SESSION_ACTIVE)) {
+            intent.extras?.getBoolean(EXTRA_MEDIA_SESSION_ACTIVE)?.let {
+                Log.d(TAG, "Setting media session active = $it")
+                mediaSession.isActive = it
+            }
+        }
+
+        intent.getStringExtra(EXTRA_MEDIA_SESSION_TITLE)?.let {
+            // We expect the media session title to be url encoded.
+            // This is needed to be able to set arbitrary titles over adb
+            val title: String = URLDecoder.decode(it, "UTF-8")
+            Log.d(TAG, "Setting media session title = $title")
+            mediaSession.setMetadata(
+                MediaMetadata.Builder()
+                    .putText(MediaMetadata.METADATA_KEY_TITLE, title)
+                    .build()
+            )
+        }
+
+        if (intent.hasExtra(EXTRA_MEDIA_SESSION_ACTIONS)) {
+            val requestedActions =
+                intent.getLongExtra(EXTRA_MEDIA_SESSION_ACTIONS, ACTION_PAUSE or ACTION_PLAY)
+            mediaSession.setPlaybackState(playbackBuilder.setActions(requestedActions).build())
+        }
+    }
+
+    /** Calls [android.app.Activity.setTurnScreenOn] if needed. */
+    private fun handleScreenExtras(intent: Intent) {
+        if (intent.getBooleanExtra(EXTRA_TURN_ON_SCREEN, false)) {
+            Log.d(TAG, "Setting setTurnScreenOn")
+            setTurnScreenOn(true)
+        }
+    }
+
+    private fun buildPipParams(bundle: Bundle?): PictureInPictureParams {
+        val builder = PictureInPictureParams.Builder()
+        bundle?.run {
+            if (containsKey(EXTRA_ASPECT_RATIO_NUMERATOR) &&
+                containsKey(EXTRA_ASPECT_RATIO_DENOMINATOR)) {
+                builder.setAspectRatio(Rational(
+                    getInt(EXTRA_ASPECT_RATIO_NUMERATOR),
+                    getInt(EXTRA_ASPECT_RATIO_DENOMINATOR)))
+            }
+
+            getString(EXTRA_SOURCE_RECT_HINT)?.let {
+                builder.setSourceRectHint(Rect.unflattenFromString(it))
+            }
+
+            getParcelableArrayList<RemoteAction>(EXTRA_SET_CUSTOM_ACTIONS)?.let { actions ->
+                val names = actions.joinToString(", ") { it.title }
+                Log.d(TAG, "Setting custom pip actions: $names")
+                builder.setActions(actions)
+            }
+        }
+        return builder.build()
+    }
+
+    /** Just set the playback state without updating the position or playback speed. */
+    private fun PlaybackState.Builder.setState(state: Int) = apply {
+        setState(state, 0, 0f)
+    }
+
+    override fun onDestroy() {
+        unregisterReceiver(broadcastReceiver)
+        super.onDestroy()
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/systemui/common/Android.bp b/tests/tests/systemui/common/Android.bp
new file mode 100644
index 0000000..7a2d035
--- /dev/null
+++ b/tests/tests/systemui/common/Android.bp
@@ -0,0 +1,19 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_helper_library {
+    name: "systemui-cts-common",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.kt"],
+}
diff --git a/tests/tests/systemui/common/src/android/systemui/tv/cts/TestEntities.kt b/tests/tests/systemui/common/src/android/systemui/tv/cts/TestEntities.kt
new file mode 100644
index 0000000..f183775
--- /dev/null
+++ b/tests/tests/systemui/common/src/android/systemui/tv/cts/TestEntities.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.systemui.tv.cts
+
+import android.content.ComponentName
+
+private const val pkg = "android.systemui.cts.tv.pip"
+
+object Components {
+
+    @JvmStatic
+    fun ComponentName.activityName(): String = flattenToShortString()
+
+    @JvmStatic
+    fun ComponentName.windowName(): String = flattenToString()
+
+    @JvmField
+    val PIP_ACTIVITY: ComponentName = ComponentName.createRelative(pkg, ".PipTestActivity")
+
+    @JvmField
+    val PIP_MENU_ACTIVITY: ComponentName = ComponentName.createRelative(
+        ResourceNames.SYSTEM_UI_PACKAGE,
+        ResourceNames.WM_SHELL_PACKAGE + ".pip.tv.PipMenuActivity"
+    )
+
+    @JvmField
+    val KEYBOARD_ACTIVITY: ComponentName = ComponentName.createRelative(pkg, ".KeyboardActivity")
+}
+
+object PipActivity {
+    /** Instruct the app to go into pip mode */
+    const val ACTION_ENTER_PIP = "$pkg.PipTestActivity.enter_pip"
+    const val ACTION_SET_MEDIA_TITLE = "$pkg.PipTestActivity.set_media_title"
+
+    /**
+     * A no-op action that the app's broadcast receiver listens to.
+     *
+     * This action can be used to apply changes passed in extras without having to
+     * launch the activity thereby moving it out of pip.
+     */
+    const val ACTION_NO_OP = "$pkg.PipTestActivity.generic_update"
+
+    const val ACTION_MEDIA_PLAY = "$pkg.PipTestActivity.media_play"
+    const val ACTION_MEDIA_PAUSE = "$pkg.PipTestActivity.media_pause"
+
+    /** Instruct the app to go into pip mode when set to true */
+    const val EXTRA_ENTER_PIP = "enter_pip"
+
+    /** Provide a rect hint for entering pip in the form "left top right bottom" */
+    const val EXTRA_SOURCE_RECT_HINT = "source_rect_hint"
+
+    /**
+     * Boolean.
+     * Make sure the app will turn on the screen (waking up the device) upon start.
+     * This is accomplished by means of
+     * https://developer.android.com/reference/android/app/Activity#setTurnScreenOn(boolean)
+     */
+    const val EXTRA_TURN_ON_SCREEN = "turn_on_screen"
+
+    const val EXTRA_ASPECT_RATIO_DENOMINATOR = "aspect_ratio_denominator"
+    const val EXTRA_ASPECT_RATIO_NUMERATOR = "aspect_ratio_numerator"
+
+    /** Taken from [android.server.wm.PinnedStackTests] */
+    object Ratios {
+        // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio
+        const val MIN_ASPECT_RATIO_NUMERATOR = 100
+        const val MIN_ASPECT_RATIO_DENOMINATOR = 239
+
+        // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio
+        const val MAX_ASPECT_RATIO_NUMERATOR = 239
+        const val MAX_ASPECT_RATIO_DENOMINATOR = 100
+    }
+
+    /** URL encoded string. Sets the title of the media session. */
+    const val EXTRA_MEDIA_SESSION_TITLE = "media_session_title"
+    /** Boolean. Controls the active status of the media session. */
+    const val EXTRA_MEDIA_SESSION_ACTIVE = "media_session_active"
+
+    /**
+     * Allows to set the [android.media.session.PlaybackState.Actions] that the media
+     * session will react to. Defaults to (ACTION_PAUSE | ACTION_PLAY).
+     */
+    const val EXTRA_MEDIA_SESSION_ACTIONS = "media_session_actions"
+
+    const val MEDIA_SESSION_TITLE = "PipTestActivity:MediaSession"
+
+    /** Set the pip menu custom actions to this [ArrayList] of [android.app.RemoteAction]. */
+    const val EXTRA_SET_CUSTOM_ACTIONS = "set_custom_actions"
+}
+
+object KeyboardActivity {
+    const val ACTION_SHOW_KEYBOARD = "$pkg.KeyboardActivity.show_keyboard"
+    const val ACTION_HIDE_KEYBOARD = "$pkg.KeyboardActivity.hide_keyboard"
+}
+
+object PipMenu {
+    const val ACTION_MENU = "PipNotification.menu"
+    const val ACTION_CLOSE = "PipNotification.close"
+}
+
+object TVNotificationExtender {
+    const val EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS"
+    const val EXTRA_CONTENT_INTENT = "content_intent"
+    const val EXTRA_DELETE_INTENT = "delete_intent"
+}
+
+object ResourceNames {
+    const val SYSTEM_UI_CTS_PACKAGE = "android.systemui.cts"
+    const val SYSTEM_UI_PACKAGE = "com.android.systemui"
+    const val WM_SHELL_PACKAGE = "com.android.wm.shell"
+
+    /** The name of the soft keyboard window. */
+    const val WINDOW_NAME_INPUT_METHOD = "InputMethod"
+
+    const val STRING_PIP_MENU_BOUNDS = "pip_menu_bounds"
+
+    const val ID_PIP_MENU_CLOSE_BUTTON = "$WM_SHELL_PACKAGE:id/close_button"
+    const val ID_PIP_MENU_FULLSCREEN_BUTTON = "$WM_SHELL_PACKAGE:id/full_button"
+    const val ID_PIP_MENU_PLAY_PAUSE_BUTTON = "$WM_SHELL_PACKAGE:id/play_pause_button"
+    const val ID_PIP_MENU_CUSTOM_BUTTON = "$WM_SHELL_PACKAGE:id/button"
+}
+
+object ShellCommands {
+    /** Execute this with the component identifier to grant notification access. */
+    const val CMD_TEMPLATE_NOTIFICATION_ALLOW_LISTENER = "cmd notification allow_listener %s"
+    /** Execute this with the component identifier to remove notification access. */
+    const val CMD_TEMPLATE_NOTIFICATION_DISALLOW_LISTENER = "cmd notification disallow_listener %s"
+}
\ No newline at end of file
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/BasicPipTests.kt b/tests/tests/systemui/src/android/systemui/cts/tv/BasicPipTests.kt
new file mode 100644
index 0000000..b80cdd4
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/BasicPipTests.kt
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.systemui.cts.tv
+
+import android.Manifest.permission.READ_DREAM_STATE
+import android.Manifest.permission.WRITE_DREAM_STATE
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.ComponentName
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.ServiceManager
+import android.platform.test.annotations.Postsubmit
+import android.server.wm.Condition
+import android.server.wm.UiDeviceUtils
+import android.server.wm.annotation.Group2
+import android.service.dreams.DreamService
+import android.service.dreams.IDreamManager
+import android.systemui.tv.cts.Components.PIP_ACTIVITY
+import android.systemui.tv.cts.Components.PIP_MENU_ACTIVITY
+import android.systemui.tv.cts.Components.windowName
+import android.systemui.tv.cts.PipActivity
+import android.systemui.tv.cts.PipActivity.ACTION_ENTER_PIP
+import android.systemui.tv.cts.PipActivity.EXTRA_ASPECT_RATIO_DENOMINATOR
+import android.systemui.tv.cts.PipActivity.EXTRA_ASPECT_RATIO_NUMERATOR
+import android.systemui.tv.cts.PipActivity.Ratios.MAX_ASPECT_RATIO_DENOMINATOR
+import android.systemui.tv.cts.PipActivity.Ratios.MAX_ASPECT_RATIO_NUMERATOR
+import android.systemui.tv.cts.PipActivity.Ratios.MIN_ASPECT_RATIO_DENOMINATOR
+import android.systemui.tv.cts.PipActivity.Ratios.MIN_ASPECT_RATIO_NUMERATOR
+import android.systemui.tv.cts.PipMenu
+import android.systemui.tv.cts.ResourceNames.ID_PIP_MENU_CLOSE_BUTTON
+import android.systemui.tv.cts.ResourceNames.ID_PIP_MENU_FULLSCREEN_BUTTON
+import android.systemui.tv.cts.ResourceNames.ID_PIP_MENU_PLAY_PAUSE_BUTTON
+import android.util.Size
+import android.view.Gravity
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.ThrowingSupplier
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+/**
+ * Tests most basic picture in picture (PiP) behavior.
+ *
+ * Build/Install/Run:
+ * atest CtsSystemUiTestCases:BasicPipTests
+ */
+@Postsubmit
+@Group2
+@RunWith(AndroidJUnit4::class)
+class BasicPipTests : PipTestBase() {
+
+    private val pipGravity: Int = resources.getInteger(
+        com.android.internal.R.integer.config_defaultPictureInPictureGravity)
+    private val displaySize = windowManager.maximumWindowMetrics.bounds
+
+    private val defaultPipAspectRatio: Float = resources.getFloat(
+        com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio)
+    private val minPipAspectRatio: Float = resources.getFloat(
+        com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio)
+    private val maxPipAspectRatio: Float = resources.getFloat(
+        com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio)
+
+    /** The size of the smaller side of the pip window. */
+    private val defaultPipSize: Float = resources.getDimension(
+        com.android.internal.R.dimen.default_minimal_size_pip_resizable_task)
+    private val screenEdgeInsetString = resources.getString(
+        com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets)
+    private val screenEdgeInsets: Point = Size.parseSize(screenEdgeInsetString).let {
+        val displayMetrics = resources.displayMetrics
+        Point(dipToPx(it.width, displayMetrics), dipToPx(it.height, displayMetrics))
+    }
+
+    @After
+    fun tearDown() {
+        stopPackage(PIP_ACTIVITY.packageName)
+    }
+
+    /** Open an app in pip mode and ensure it has a window but is not focused. */
+    @Test
+    fun openPip_launchedNotFocused() {
+        launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP)
+        waitForEnterPip(PIP_ACTIVITY)
+
+        assertLaunchedNotFocused(PIP_ACTIVITY)
+    }
+
+    /** Ensure an app can be launched into pip mode from the screensaver state. */
+    @Test
+    fun openPip_afterScreenSaver() {
+        runWithDreamManager { dreamManager ->
+            dreamManager.dream()
+            dreamManager.waitForDream()
+        }
+
+        // Launch pip activity that is supposed to wake up the device
+        launchActivity(
+            activity = PIP_ACTIVITY,
+            action = ACTION_ENTER_PIP,
+            boolExtras = mapOf(PipActivity.EXTRA_TURN_ON_SCREEN to true)
+        )
+        waitForEnterPip(PIP_ACTIVITY)
+
+        assertLaunchedNotFocused(PIP_ACTIVITY)
+        assertTrue("Device must be awake") {
+            runWithDreamManager { dreamManager ->
+                !dreamManager.isDreaming
+            }
+        }
+    }
+
+    /** Ensure an app in pip mode remains open throughout the device dreaming and waking. */
+    @Test
+    fun pipApp_remainsOpen_afterScreensaver() {
+        launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP)
+        waitForEnterPip(PIP_ACTIVITY)
+
+        runWithDreamManager { dreamManager ->
+            dreamManager.dream()
+            dreamManager.waitForDream()
+            dreamManager.awaken()
+            dreamManager.waitForAwake()
+        }
+
+        assertLaunchedNotFocused(PIP_ACTIVITY)
+    }
+
+    /** Open an app in pip mode and ensure it is located at the expected default position. */
+    @Test
+    fun openPip_position_defaultAspectRatio() {
+        launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP)
+        assertPipWindowPosition(PIP_ACTIVITY, defaultPipAspectRatio)
+    }
+
+    /** Open an app in pip mode with minimal aspect ratio and ensure its position is correct. */
+    @Test
+    fun openPip_position_minAspectRatio() {
+        launchPipWithAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR)
+        assertPipWindowPosition(PIP_ACTIVITY, minPipAspectRatio)
+    }
+
+    /** Open an app in pip mode with maximal aspect ratio and ensure its position is correct. */
+    @Test
+    fun openPip_position_maxAspectRatio() {
+        launchPipWithAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR)
+        assertPipWindowPosition(PIP_ACTIVITY, maxPipAspectRatio)
+    }
+
+    /** Ensure the pip window keeps its aspect ratio after the pip menu is dismissed. */
+    @Test
+    fun pipMenu_restoresAspectRatio_onExit() {
+        // start pip with maximum aspect ratio
+        launchPipWithAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR)
+        assertPipWindowPosition(PIP_ACTIVITY, maxPipAspectRatio)
+
+        // open pip menu
+        sendBroadcast(PipMenu.ACTION_MENU)
+        waitForFullscreen(PIP_MENU_ACTIVITY)
+
+        // back out of the menu
+        UiDeviceUtils.pressBackButton()
+        wmState.waitAndAssertActivityRemoved(PIP_MENU_ACTIVITY)
+
+        // now ensure the window kept its initial maximum aspect ratio
+        assertPipWindowPosition(PIP_ACTIVITY, maxPipAspectRatio)
+    }
+
+    /** Open an app in pip mode and ensure its pip menu can be opened. */
+    @Test
+    fun pipMenu_open() {
+        launchPipThenEnterMenu()
+        assertPipMenuOpen()
+    }
+
+    /** Ensure the [android.view.KeyEvent.KEYCODE_WINDOW] correctly opens the pip menu. */
+    @Test
+    fun pipMenu_open_onWindowButtonPress() {
+        launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP)
+        waitForEnterPip(PIP_ACTIVITY)
+        // enter pip menu
+        uiDevice.pressKeyCode(KeyEvent.KEYCODE_WINDOW)
+        assertPipMenuOpen()
+    }
+
+    /** Ensure the pip menu opens in the expected location. */
+    @Test
+    fun pipMenu_correctLocation() {
+        launchPipThenEnterMenu()
+
+        waitForWMState("The PiP menu must be in the right place!") {
+            val pipTask = it.getTaskByActivity(PIP_ACTIVITY, WINDOWING_MODE_PINNED)
+            pipTask.bounds == menuModePipBounds
+        }
+    }
+
+    /** Open an app's pip menu then press its close button and ensure the app is closed. */
+    @Test
+    fun pipMenu_openThenClose() {
+        launchPipThenEnterMenu()
+
+        val closeButton = locateByResourceName(ID_PIP_MENU_CLOSE_BUTTON)
+        closeButton.click()
+
+        waitForWMState("The PiP app and its menu must be closed!") { state ->
+            !state.containsActivity(PIP_MENU_ACTIVITY) &&
+                !state.isActivityVisible(PIP_ACTIVITY)
+        }
+    }
+
+    /** Open an app's pip menu then press its fullscreen button and ensure the app is fullscreen. */
+    @Test
+    fun pipMenu_openThenFullscreen() {
+        launchPipThenEnterMenu()
+
+        val fullscreenButton = locateByResourceName(ID_PIP_MENU_FULLSCREEN_BUTTON)
+        fullscreenButton.click()
+        waitForFullscreen(PIP_ACTIVITY)
+
+        wmState.waitAndAssertActivityRemoved(PIP_MENU_ACTIVITY)
+        wmState.assertFocusedActivity("The PiP app must be focused!", PIP_ACTIVITY)
+        assertTrue("The PiP app must be in fullscreen mode!") {
+            wmState.containsActivityInWindowingMode(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN)
+        }
+    }
+
+    /** Ensure the pip menu contains a media control button when there is playback. */
+    @Test
+    fun pipMenu_containsMediaButton() {
+        // launch a pip app, activate its media session, and start media playback
+        launchActivity(
+            activity = PIP_ACTIVITY,
+            action = PipActivity.ACTION_MEDIA_PLAY,
+            boolExtras = mapOf(
+                PipActivity.EXTRA_ENTER_PIP to true,
+                PipActivity.EXTRA_MEDIA_SESSION_ACTIVE to true
+            ),
+            stringExtras = mapOf(PipActivity.EXTRA_MEDIA_SESSION_TITLE to "Playback")
+        )
+        waitForEnterPip(PIP_ACTIVITY)
+
+        // enter pip menu
+        sendBroadcast(PipMenu.ACTION_MENU)
+        waitForFullscreen(PIP_MENU_ACTIVITY)
+        assertPipMenuOpen()
+
+        // the media control button has to be present in the pip menu
+        locateByResourceName(ID_PIP_MENU_PLAY_PAUSE_BUTTON)
+    }
+
+    /** Open an app's pip menu then press back and ensure the app is back in pip. */
+    @Test
+    fun pipMenu_openThenBack() {
+        launchPipThenEnterMenu()
+        uiDevice.pressBack()
+
+        assertActivityInPip(PIP_ACTIVITY)
+    }
+
+    /** Open an app's pip menu then press home and ensure the app is back in pip. */
+    @Test
+    fun pipMenu_openThenHome() {
+        launchPipThenEnterMenu()
+        uiDevice.pressHome()
+
+        assertActivityInPip(PIP_ACTIVITY)
+    }
+
+    /**  Open an app in pip mode and set the given aspect ratio for its pip window. */
+    private fun launchPipWithAspectRatio(numerator: Int, denominator: Int) {
+        launchActivity(
+            PIP_ACTIVITY,
+            ACTION_ENTER_PIP,
+            intExtras = mapOf(
+                EXTRA_ASPECT_RATIO_NUMERATOR to numerator,
+                EXTRA_ASPECT_RATIO_DENOMINATOR to denominator
+            )
+        )
+    }
+
+    /** Assert that the given activity is in pip mode and the pip menu is gone. */
+    private fun assertActivityInPip(activity: ComponentName) {
+        wmState.waitAndAssertActivityRemoved(PIP_MENU_ACTIVITY)
+        wmState.assertNotFocusedActivity("The PiP app must not be focused!", activity)
+        assertTrue("The PiP app must be back in pip mode after dismissing the pip menu!") {
+            wmState.containsActivityInWindowingMode(activity, WINDOWING_MODE_PINNED)
+        }
+    }
+
+    /** Launches an app into pip mode then opens the pip menu. */
+    private fun launchPipThenEnterMenu() {
+        launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP)
+        waitForEnterPip(PIP_ACTIVITY)
+        // enter pip menu
+        sendBroadcast(PipMenu.ACTION_MENU)
+        waitForFullscreen(PIP_MENU_ACTIVITY)
+    }
+
+    /** Ensure the pip window has the correct dimensions and position for a given [aspectRatio]. */
+    private fun assertPipWindowPosition(activity: ComponentName, aspectRatio: Float) {
+        waitForEnterPip(PIP_ACTIVITY)
+
+        val pipTask = wmState.getTaskByActivity(activity, WINDOWING_MODE_PINNED)
+        assertEquals(
+            expected = expectedPipBounds(aspectRatio),
+            actual = pipTask.bounds,
+            message = "The PiP window must be at the expected location!"
+        )
+    }
+
+    /** Calculates the pip window bounds given the [aspectRatio]. */
+    private fun expectedPipBounds(aspectRatio: Float): Rect = Rect().apply {
+        // defaultPipSize is always the size of the smaller side
+        val (width, height) =
+            if (aspectRatio <= 1.0f) {
+                // portrait orientation, the width is smaller
+                defaultPipSize to defaultPipSize / aspectRatio
+            } else {
+                // landscape, the height is smaller
+                defaultPipSize * aspectRatio to defaultPipSize
+            }
+
+        Gravity.apply(pipGravity, width.toInt(), height.toInt(),
+            displaySize, screenEdgeInsets.x, screenEdgeInsets.y, this)
+    }
+
+    private fun assertLaunchedNotFocused(activity: ComponentName) {
+        wmState.assertActivityDisplayed(activity)
+        wmState.assertNotFocusedWindow(
+            "PiP Window must not be focused!",
+            activity.windowName()
+        )
+    }
+
+    /** Run the given actions on a dream manager, acquiring appropriate permissions.  */
+    private fun <T> runWithDreamManager(actions: (IDreamManager) -> T): T {
+        val dreamManager: IDreamManager = IDreamManager.Stub.asInterface(
+            ServiceManager.getServiceOrThrow(DreamService.DREAM_SERVICE))
+
+        return SystemUtil.runWithShellPermissionIdentity(ThrowingSupplier {
+            actions(dreamManager)
+        }, READ_DREAM_STATE, WRITE_DREAM_STATE)
+    }
+
+    /** Wait for the device to enter dream state. Throw on timeout. */
+    private fun IDreamManager.waitForDream() {
+        val message = "Device must be dreaming!"
+        Condition.waitFor(message) {
+            isDreaming
+        } || error(message)
+    }
+
+    /** Wait for the device to awaken. Throw on timeout. */
+    private fun IDreamManager.waitForAwake() {
+        val message = "Device must be awake!"
+        Condition.waitFor(message) {
+            !isDreaming
+        } || error(message)
+    }
+}
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/CustomPipActionsTests.kt b/tests/tests/systemui/src/android/systemui/cts/tv/CustomPipActionsTests.kt
new file mode 100644
index 0000000..16d6a63
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/CustomPipActionsTests.kt
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.systemui.cts.tv
+
+import android.app.ActivityTaskManager
+import android.app.PendingIntent
+import android.app.RemoteAction
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.platform.test.annotations.Postsubmit
+import android.server.wm.UiDeviceUtils
+import android.server.wm.WindowManagerState
+import android.server.wm.annotation.Group2
+import android.systemui.tv.cts.Components.PIP_ACTIVITY
+import android.systemui.tv.cts.Components.PIP_MENU_ACTIVITY
+import android.systemui.tv.cts.PipActivity
+import android.systemui.tv.cts.PipActivity.ACTION_MEDIA_PLAY
+import android.systemui.tv.cts.PipActivity.ACTION_NO_OP
+import android.systemui.tv.cts.PipActivity.EXTRA_ENTER_PIP
+import android.systemui.tv.cts.PipActivity.EXTRA_SET_CUSTOM_ACTIONS
+import android.systemui.tv.cts.PipMenu
+import android.systemui.tv.cts.ResourceNames.ID_PIP_MENU_CLOSE_BUTTON
+import android.systemui.tv.cts.ResourceNames.ID_PIP_MENU_CUSTOM_BUTTON
+import android.systemui.tv.cts.ResourceNames.ID_PIP_MENU_FULLSCREEN_BUTTON
+import android.systemui.tv.cts.ResourceNames.ID_PIP_MENU_PLAY_PAUSE_BUTTON
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+/**
+ * Tests custom picture in picture (PiP) actions.
+ *
+ * Build/Install/Run:
+ * atest CtsSystemUiTestCases:CustomPipActionsTests
+ */
+@Postsubmit
+@Group2
+@RunWith(AndroidJUnit4::class)
+class CustomPipActionsTests : PipTestBase() {
+
+    // reuse the icon from another test for our purposes
+    private val icon: Icon =
+        Icon.createWithResource(resources, android.systemui.cts.R.drawable.ic_save)
+
+    private val maxPipActions: Int =
+        ActivityTaskManager.getService().getMaxNumPictureInPictureActions(context.activityToken)
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+        UiDeviceUtils.pressHomeButton()
+    }
+
+    @After
+    fun tearDown() {
+        stopPackage(PIP_ACTIVITY.packageName)
+    }
+
+    /** Ensure the pip menu contains a custom action button if set by the user. */
+    @Test
+    fun pipMenu_contains_CustomActionButton() {
+        val action = "Custom Action"
+        launchPipMenuWithActions(createClearActionsList(listOf(action)))
+        assertActionButtonPresent(action)
+    }
+
+    /** Ensure it is possible to set [maxPipActions] many custom actions. */
+    @Test
+    fun pipMenu_canHave_maxNumberOfActions() {
+        val titles = List(maxPipActions) { num: Int ->
+            "Action $num"
+        }
+        val actions = createClearActionsList(titles)
+
+        launchPipMenuWithActions(actions)
+
+        titles.forEach { title ->
+            assertActionButtonPresent(title)
+        }
+
+        // also ensure the default buttons are still there
+        assertFullscreenAndCloseButtons()
+    }
+
+    /** Ensure the pip menu cannot display more than [maxPipActions] custom actions. */
+    @Test
+    fun pipMenu_cannotHave_moreThan_maxNumberOfActions() {
+        val titles = List(maxPipActions + 1) { num ->
+            "Action $num"
+        }
+        val actions = createClearActionsList(titles)
+
+        launchPipMenuWithActions(actions)
+
+        // ensure the first maxPipActions are there
+        titles.take(maxPipActions).forEach { title ->
+            assertActionButtonPresent(title)
+        }
+
+        // also make sure the last button with is absent
+        assertActionButtonGone(titles.last())
+    }
+
+    /** Ensure it's possible for a custom action to clear all custom actions (including itself). */
+    @Test
+    fun pipMenu_customAction_canClear_allActions() {
+        val titles = List(maxPipActions) { num: Int ->
+            "Action $num"
+        }
+        launchPipMenuWithActions(createClearActionsList(titles))
+
+        // click on a random button
+        val clearButton = assertNotNull(findActionButton(titles.random()))
+        clearButton.click()
+
+        // and ensure there are no custom actions
+        titles.forEach { title ->
+            assertActionButtonGone(title)
+        }
+
+        // ensure the default buttons are still there
+        assertFullscreenAndCloseButtons()
+    }
+
+    /** Ensure that custom controls override media controls on app startup. */
+    @Test
+    fun pipMenu_customActions_overrideMediaSessionControls() {
+        // Start a pip app with an active playing media session and custom controls
+        val intent = makeStartPipIntent().apply {
+            setCustomActions(createClearActionsList(listOf("Custom Action")))
+            startMediaSession()
+        }
+        startAndOpenPipMenu(intent)
+
+        assertMediaControlsGone()
+    }
+
+    /** Ensure that media controls disappear when custom controls are set. */
+    @Test
+    fun pipMenu_customActions_removeMediaSessionControls() {
+        // first start a pip activity with an active media session
+        val mediaPip = makeStartPipIntent().startMediaSession()
+        startAndOpenPipMenu(mediaPip)
+        assertMediaControlsPresent()
+
+        // now set the custom actions
+        val customActionsPip =
+            makeUpdatePipIntent().setCustomActions(createClearActionsList(listOf("Custom Action")))
+        context.sendBroadcast(customActionsPip)
+        // make sure the media controls are gone
+        assertMediaControlsGone()
+
+        // remove the custom controls again
+        val removeCustomActions = makeUpdatePipIntent().setCustomActions(arrayListOf())
+        context.sendBroadcast(removeCustomActions)
+        // make sure the media button is present again
+        assertMediaControlsPresent()
+    }
+
+    /** Ensure the pip menu is not dismissed when custom actions are set while it is open. */
+    @Test
+    fun pipMenu_doesNotClose_whenUpdating_customActions() {
+        startAndOpenPipMenu(makeStartPipIntent())
+
+        val updateIntent =
+            makeUpdatePipIntent().setCustomActions(createClearActionsList(listOf("Custom")))
+        context.sendBroadcast(updateIntent)
+
+        // wait for a potential erroneous transition into pip mode (but don't throw)
+        wmState.waitForWithAmState("back to pip mode?") { state: WindowManagerState ->
+            !state.containsActivity(PIP_MENU_ACTIVITY)
+        }
+
+        // what we actually expect is that the pip menu never went away
+        assertPipMenuOpen()
+    }
+
+    /** Find the pip media controls or throw. */
+    private fun assertMediaControlsPresent() {
+        assertTrue("Media buttons must be shown!") {
+            uiDevice.wait(Until.hasObject(By.res(ID_PIP_MENU_PLAY_PAUSE_BUTTON)), defaultTimeout)
+        }
+    }
+
+    /** Throw if pip media controls are present. */
+    private fun assertMediaControlsGone() {
+        assertTrue("No media buttons must be shown!") {
+            uiDevice.wait(Until.gone(By.res(ID_PIP_MENU_PLAY_PAUSE_BUTTON)), defaultTimeout)
+        }
+    }
+
+    /** Fail if the default fullscreen and close buttons cannot be found. */
+    private fun assertFullscreenAndCloseButtons() {
+        locateByResourceName(ID_PIP_MENU_FULLSCREEN_BUTTON)
+        locateByResourceName(ID_PIP_MENU_CLOSE_BUTTON)
+    }
+
+    /** Ensure the action with [title] exists or throw. */
+    private fun assertActionButtonPresent(title: String) {
+        assertNotNull(
+            findActionButton(title),
+            "Could not find custom action button for $title"
+        )
+    }
+
+    /** Ensure action with [title] no longer exists. */
+    private fun assertActionButtonGone(title: String) = assertTrue(
+        uiDevice.wait(Until.gone(title.asSelector()), defaultTimeout),
+        "Button $title must be gone!"
+    )
+
+    private fun findActionButton(title: String): UiObject2? = uiDevice.wait(
+        Until.findObject(title.asSelector()), defaultTimeout)
+
+    /** Constructs a selector for a custom action button with [this] title. */
+    private fun String.asSelector() = By.res(ID_PIP_MENU_CUSTOM_BUTTON).desc(this)
+
+    /**
+     * Create a remote action that will clear all remote actions.
+     * This action's title and description will be the given [title].
+     */
+    private fun createRemoteClearAction(title: String): RemoteAction =
+        RemoteAction(icon, title, title, makePendingClearBroadcast())
+
+    private fun makePendingClearBroadcast(): PendingIntent =
+        PendingIntent.getBroadcast(
+            context,
+            0,
+            Intent().apply {
+                action = ACTION_NO_OP
+                putParcelableArrayListExtra(EXTRA_SET_CUSTOM_ACTIONS, arrayListOf<RemoteAction>())
+            },
+            PendingIntent.FLAG_UPDATE_CURRENT
+        )
+
+    /** Launches a pip app with given custom actions and enters the pip menu. */
+    private fun launchPipMenuWithActions(actions: ArrayList<RemoteAction>) {
+        val intent = makeStartPipIntent().setCustomActions(actions)
+        startAndOpenPipMenu(intent)
+    }
+
+    /** Modify a pip app intent to start a media session. */
+    private fun Intent.startMediaSession(): Intent = apply {
+        action = ACTION_MEDIA_PLAY
+        putExtra(EXTRA_ENTER_PIP, true)
+        putExtra(PipActivity.EXTRA_MEDIA_SESSION_ACTIVE, true)
+        putExtra(PipActivity.EXTRA_MEDIA_SESSION_TITLE, "MediaTitle")
+    }
+
+    /** Modify a pip app intent to set custom actions. */
+    private fun Intent.setCustomActions(actions: ArrayList<RemoteAction>): Intent = apply {
+        putParcelableArrayListExtra(EXTRA_SET_CUSTOM_ACTIONS, actions)
+    }
+
+    /** Create an intent to start an app in pip mode. */
+    private fun makeStartPipIntent(): Intent = Intent().apply {
+        component = PIP_ACTIVITY
+        putExtra(EXTRA_ENTER_PIP, true)
+        addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+    }
+
+    /** Creates an intent to update an already running pip app. */
+    private fun makeUpdatePipIntent() = Intent(ACTION_NO_OP)
+
+    /** Start the app with the given intent and open its pip menu. */
+    private fun startAndOpenPipMenu(startPipActivityIntent: Intent) {
+        context.startActivity(startPipActivityIntent)
+        waitForEnterPip(PIP_ACTIVITY)
+
+        sendBroadcast(PipMenu.ACTION_MENU)
+        assertPipMenuOpen()
+    }
+
+    /** Create a list of remote actions for clearing all custom actions with the given titles. */
+    private fun createClearActionsList(titles: List<String>): ArrayList<RemoteAction> =
+        titles.mapTo(arrayListOf()) { title -> createRemoteClearAction(title) }
+}
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/FlickerPipTests.kt b/tests/tests/systemui/src/android/systemui/cts/tv/FlickerPipTests.kt
new file mode 100644
index 0000000..ee501c5
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/FlickerPipTests.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.systemui.cts.tv
+
+import android.graphics.Region
+import android.platform.test.annotations.Postsubmit
+import android.server.wm.UiDeviceUtils
+import android.server.wm.annotation.Group2
+import android.support.test.launcherhelper.TvLauncherStrategy
+import android.support.test.uiautomator.UiDevice
+import android.systemui.tv.cts.Components.KEYBOARD_ACTIVITY
+import android.systemui.tv.cts.Components.PIP_ACTIVITY
+import android.systemui.tv.cts.Components.windowName
+import android.systemui.tv.cts.KeyboardActivity.ACTION_HIDE_KEYBOARD
+import android.systemui.tv.cts.KeyboardActivity.ACTION_SHOW_KEYBOARD
+import android.systemui.tv.cts.PipActivity.ACTION_ENTER_PIP
+import android.systemui.tv.cts.ResourceNames.WINDOW_NAME_INPUT_METHOD
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.cts.mockime.ImeSettings
+import com.android.cts.mockime.MockImeSessionRule
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests slightly advanced picture in picture (PiP) behaviors.
+ *
+ * Build/Install/Run:
+ * atest CtsSystemUiTestCases:FlickerPipTests
+ */
+@Postsubmit
+@Group2
+@RunWith(AndroidJUnit4::class)
+class FlickerPipTests : PipTestBase() {
+
+    @get:Rule
+    val rule = MockImeSessionRule(
+        context,
+        instrumentation.uiAutomation,
+        ImeSettings.Builder()
+            .setInputViewHeight(windowManager.maximumWindowMetrics.bounds.height() / 3)
+    )
+
+    private val testRepetitions = 10
+
+    private val tvLauncherStrategy = TvLauncherStrategy().apply {
+        setUiDevice(UiDevice.getInstance(instrumentation))
+    }
+
+    /** Starts and stops a keyboard app and a pip app. Repeats [testRepetitions] times. */
+    private val keyboardScenario: FlickerBuilder
+        get() = FlickerBuilder(instrumentation, tvLauncherStrategy).apply {
+            repeat { testRepetitions }
+            // disable layer tracing
+            withLayerTracing { null }
+            setup {
+                test {
+                    UiDeviceUtils.pressHomeButton()
+                    // launch our target pip app
+                    launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP)
+                    waitForEnterPip(PIP_ACTIVITY)
+                    // open an app with an input field and a keyboard
+                    launchActivity(KEYBOARD_ACTIVITY)
+                    waitForFullscreen(KEYBOARD_ACTIVITY)
+                    waitForKeyboardShown()
+                }
+            }
+            teardown {
+                test {
+                    stopPackage(PIP_ACTIVITY.packageName)
+                    stopPackage(KEYBOARD_ACTIVITY.packageName)
+                }
+            }
+        }
+
+    /** Ensure the pip window remains visible throughout any keyboard interactions. */
+    @Test
+    fun pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose() {
+        val testTag = "pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose"
+        runWithFlicker(keyboardScenario) {
+            withTag { testTag }
+            transitions {
+                // open the soft keyboard
+                launchActivity(KEYBOARD_ACTIVITY, ACTION_SHOW_KEYBOARD)
+                waitForKeyboardShown()
+
+                // then close it again
+                launchActivity(KEYBOARD_ACTIVITY, ACTION_HIDE_KEYBOARD)
+                waitForKeyboardHidden()
+            }
+            assertions {
+                windowManagerTrace {
+                    all("PiP window must remain inside visible bounds") {
+                        coversAtMostRegion(
+                            partialWindowTitle = PIP_ACTIVITY.windowName(),
+                            region = Region(windowManager.maximumWindowMetrics.bounds)
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    /** Ensure the pip window does not obscure the keyboard. */
+    @Test
+    fun pipWindow_doesNotObscure_keyboard() {
+        val testTag = "pipWindow_doesNotObscure_keyboard"
+        runWithFlicker(keyboardScenario) {
+            withTag { testTag }
+            transitions {
+                // open the soft keyboard
+                launchActivity(KEYBOARD_ACTIVITY, ACTION_SHOW_KEYBOARD)
+                waitForKeyboardShown()
+            }
+            teardown {
+                eachRun {
+                    // close the keyboard
+                    launchActivity(KEYBOARD_ACTIVITY, ACTION_HIDE_KEYBOARD)
+                    waitForKeyboardHidden()
+                }
+            }
+            assertions {
+                windowManagerTrace {
+                    end {
+                        isAboveWindow(WINDOW_NAME_INPUT_METHOD, PIP_ACTIVITY.windowName())
+                    }
+                }
+            }
+        }
+    }
+
+    /** Wait for the soft keyboard window to be open or throw. */
+    private fun waitForKeyboardShown() {
+        waitForWMState("Keyboard must be shown") { state ->
+            state.isWindowVisible(WINDOW_NAME_INPUT_METHOD)
+        }
+    }
+
+    /** Wait for the soft keyboard window to be hidden or throw. */
+    private fun waitForKeyboardHidden() {
+        waitForWMState("Keyboard must be hidden") { state ->
+            !state.isWindowVisible(WINDOW_NAME_INPUT_METHOD)
+        }
+    }
+}
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/MicIndicatorTest.kt b/tests/tests/systemui/src/android/systemui/cts/tv/MicIndicatorTest.kt
new file mode 100644
index 0000000..5ffdcaf
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/MicIndicatorTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.systemui.cts.tv
+
+import android.content.ComponentName
+import android.platform.test.annotations.Postsubmit
+import android.server.wm.annotation.Group2
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests microphone indicator.
+ *
+ * Build/Install/Run:
+ * atest CtsSystemUiTestCases:MicIndicatorTest
+ */
+@Postsubmit
+@Group2
+@RunWith(AndroidJUnit4::class)
+class MicIndicatorTest : TvTestBase() {
+    companion object {
+        private val AUDIO_RECORD_API_SERVICE = ComponentName.createRelative(
+                "android.systemui.cts.audiorecorder.audiorecord", ".AudioRecorderService")
+        private val MEDIA_RECORDER_API_SERVICE = ComponentName.createRelative(
+                "android.systemui.cts.audiorecorder.mediarecorder", ".AudioRecorderService")
+
+        private const val ACTION_THROW = "android.systemui.cts.audiorecorder.ACTION_THROW"
+        private const val ACTION_STOP = "android.systemui.cts.audiorecorder.ACTION_STOP"
+        private const val ACTION_START = "android.systemui.cts.audiorecorder.ACTION_START"
+
+        private const val MIC_INDICATOR_WINDOW_TITLE = "MicrophoneCaptureIndicator"
+    }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+        assertIndicatorWindowGone()
+    }
+
+    @After
+    fun tearDown() {
+        stopPackage(AUDIO_RECORD_API_SERVICE.packageName)
+        stopPackage(MEDIA_RECORDER_API_SERVICE.packageName)
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_AudioRecordApi() {
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_STOP)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_MediaRecorderApi() {
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_STOP)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_AudioRecordApi_until_forceStopped() {
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        stopPackage(AUDIO_RECORD_API_SERVICE.packageName)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_MediaRecorderApi_until_forceStopped() {
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        stopPackage(MEDIA_RECORDER_API_SERVICE.packageName)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_AudioRecordApi_until_crashed() {
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_THROW)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_MediaRecorderApi_until_crashed() {
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_THROW)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsingBothApisSimultaneously() {
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_STOP)
+        // The indicator should stay on, since the MR is still running.
+        assertIndicatorWindowVisible()
+
+        // Give it 5s, and make sure indicator is still there.
+        Thread.sleep(5_000)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_STOP)
+        // Now it should go.
+        assertIndicatorWindowGone()
+    }
+
+    private fun assertIndicatorWindowVisible() {
+        wmState.waitFor("Waiting for the mic indicator window to come up") {
+            it.containsWindow(MIC_INDICATOR_WINDOW_TITLE) &&
+                    it.isWindowVisible(MIC_INDICATOR_WINDOW_TITLE)
+        } || error("Mic indicator window is not visible.")
+    }
+
+    private fun assertIndicatorWindowGone() {
+        wmState.waitFor("Waiting for the mic indicator window to disappear") {
+            !it.containsWindow(MIC_INDICATOR_WINDOW_TITLE)
+        } || error("Mic indicator window is present (should be gone).")
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/NotificationUtils.kt b/tests/tests/systemui/src/android/systemui/cts/tv/NotificationUtils.kt
new file mode 100644
index 0000000..d8889dd
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/NotificationUtils.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("NotificationUtils")
+package android.systemui.cts.tv
+
+import android.Manifest.permission.GET_INTENT_SENDER_INTENT
+import android.app.Notification.EXTRA_TITLE
+import android.app.PendingIntent
+import android.content.Intent
+import android.service.notification.StatusBarNotification
+import android.systemui.tv.cts.TVNotificationExtender
+import com.android.compatibility.common.util.SystemUtil
+import java.net.URLEncoder
+
+/** Extract a pending intent that was put by a [android.app.Notification.TvExtender]. */
+fun StatusBarNotification.pendingTvIntent(key: String): PendingIntent =
+    notification?.extras?.getBundle(TVNotificationExtender.EXTRA_TV_EXTENDER)?.getParcelable(key)
+        ?: error("No pending intent found for key $key")
+
+/** Retrieve the inner intent of a pending intent. */
+val PendingIntent.innerIntent: Intent
+    get() = SystemUtil.runWithShellPermissionIdentity(::getIntent, GET_INTENT_SENDER_INTENT)
+
+fun StatusBarNotification.title(): String = notification?.extras?.getString(EXTRA_TITLE) ?: ""
+
+internal fun String.urlEncoded(): String = URLEncoder.encode(this, "UTF-8")
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/PipNotificationListenerService.kt b/tests/tests/systemui/src/android/systemui/cts/tv/PipNotificationListenerService.kt
new file mode 100644
index 0000000..0a4252b
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/PipNotificationListenerService.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.systemui.cts.tv
+
+import android.content.ComponentName
+import android.server.wm.Condition
+import android.service.notification.NotificationListenerService
+import android.service.notification.StatusBarNotification
+import android.systemui.tv.cts.ResourceNames
+import android.systemui.tv.cts.ResourceNames.SYSTEM_UI_CTS_PACKAGE
+import javax.annotation.concurrent.GuardedBy
+
+/**
+ * This service exposes pip notifications to the tests.
+ */
+class PipNotificationListenerService : NotificationListenerService() {
+    /**
+     * Stores notifications mapped by their notifying app uid and the notification id.
+     * We cannot store these efficiently in a set as they only support referential equality.
+     */
+    @GuardedBy("this")
+    private val _activeNotifications = mutableMapOf<Pair<Int, Int>, StatusBarNotification>()
+
+    /** Currently active notifications from the [ResourceNames.SYSTEM_UI_PACKAGE] package. */
+    val activePipNotifications: List<StatusBarNotification>
+        get() = synchronized(this) { _activeNotifications.values.toList() }
+
+    /** Clear the internal active and removed notification sets. */
+    fun clearNotifications() {
+        synchronized(this) {
+            _activeNotifications.clear()
+        }
+    }
+
+    /** Find a notification by the given title or return null. */
+    fun findActivePipNotification(title: String): StatusBarNotification? =
+        Condition.waitForResult("find notification with title $title") { condition ->
+            condition.setResultSupplier {
+                activePipNotifications.find { it.title() == title }
+            }
+            condition.setResultValidator { it != null }
+            condition.setReturnLastResult(true)
+        }
+
+    override fun onNotificationPosted(sbn: StatusBarNotification?) {
+        if (sbn?.packageName == ResourceNames.SYSTEM_UI_PACKAGE) {
+            synchronized(this) {
+                _activeNotifications[sbn.asKey()] = sbn
+            }
+        }
+    }
+
+    override fun onNotificationRemoved(sbn: StatusBarNotification?) {
+        if (sbn?.packageName == ResourceNames.SYSTEM_UI_PACKAGE) {
+            synchronized(this) {
+                _activeNotifications.remove(sbn.asKey())
+            }
+        }
+    }
+
+    /**
+     * Produce a unique key for this notification.
+     * Specifically, this is a pair of notifying app uid and the notification id.
+     */
+    private fun StatusBarNotification.asKey() = uid to id
+
+    override fun onListenerConnected() {
+        instance = this
+    }
+
+    override fun onListenerDisconnected() {
+        instance = null
+    }
+
+    companion object {
+        @JvmField
+        val componentName: ComponentName = ComponentName(
+            SYSTEM_UI_CTS_PACKAGE,
+            PipNotificationListenerService::class.java.canonicalName
+        )
+
+        /** Expose this to our tests */
+        @get:JvmStatic
+        internal var instance: PipNotificationListenerService? = null
+            private set
+    }
+}
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/PipNotificationTests.kt b/tests/tests/systemui/src/android/systemui/cts/tv/PipNotificationTests.kt
new file mode 100644
index 0000000..afc8a5a
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/PipNotificationTests.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.systemui.cts.tv
+
+import android.app.NotificationManager
+import android.content.Intent
+import android.platform.test.annotations.Postsubmit
+import android.server.wm.annotation.Group2
+import android.systemui.tv.cts.Components.PIP_ACTIVITY
+import android.systemui.tv.cts.PipActivity
+import android.systemui.tv.cts.PipActivity.ACTION_ENTER_PIP
+import android.systemui.tv.cts.PipActivity.EXTRA_MEDIA_SESSION_ACTIVE
+import android.systemui.tv.cts.PipActivity.EXTRA_MEDIA_SESSION_TITLE
+import android.systemui.tv.cts.PipMenu.ACTION_CLOSE
+import android.systemui.tv.cts.PipMenu.ACTION_MENU
+import android.systemui.tv.cts.ShellCommands.CMD_TEMPLATE_NOTIFICATION_ALLOW_LISTENER
+import android.systemui.tv.cts.ShellCommands.CMD_TEMPLATE_NOTIFICATION_DISALLOW_LISTENER
+import android.systemui.tv.cts.TVNotificationExtender.EXTRA_CONTENT_INTENT
+import android.systemui.tv.cts.TVNotificationExtender.EXTRA_DELETE_INTENT
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+/**
+ * Tests notification-related (PiP) behavior.
+ *
+ * Build/Install/Run:
+ * atest CtsSystemUiTestCases:PipNotificationTests
+ */
+@Postsubmit
+@Group2
+@RunWith(AndroidJUnit4::class)
+class PipNotificationTests : PipTestBase() {
+    private val notificationManager: NotificationManager =
+        context.getSystemService(NotificationManager::class.java)
+            ?: error("Could not find a NotificationManager!")
+
+    init {
+        val intent = Intent(context, PipNotificationListenerService::class.java)
+        context.startService(intent)
+    }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+        toggleListenerAccess(allow = true)
+        notificationListener.clearNotifications()
+    }
+
+    @After
+    fun tearDown() {
+        stopPackage(PIP_ACTIVITY.packageName)
+        toggleListenerAccess(allow = false)
+    }
+
+    /** Ensure a notification is posted when an app is in pip mode. */
+    @Test
+    fun pipNotification_isPosted() {
+        launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP)
+        waitForEnterPip(PIP_ACTIVITY)
+
+        assertNotNull(notificationListener.findActivePipNotification(PIP_ACTIVITY.packageName))
+    }
+
+    /** Ensure the pip notification has a functional pending intent to show the pip menu. */
+    @Test
+    fun pipNotification_detailsButton() {
+        launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP)
+        waitForEnterPip(PIP_ACTIVITY)
+
+        val notification =
+            assertNotNull(notificationListener.findActivePipNotification(PIP_ACTIVITY.packageName))
+
+        val contentIntent = notification.pendingTvIntent(EXTRA_CONTENT_INTENT)
+        assertTrue("The notification content intent must have action $ACTION_MENU") {
+            Intent(ACTION_MENU).filterEquals(contentIntent.innerIntent)
+        }
+
+        contentIntent.send()
+        assertPipMenuOpen()
+    }
+
+    /** Ensure the pip notification has a functional pending intent to dismiss the app. */
+    @Test
+    fun pipNotification_dismissButton() {
+        launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP)
+        waitForEnterPip(PIP_ACTIVITY)
+
+        val notification =
+            assertNotNull(notificationListener.findActivePipNotification(PIP_ACTIVITY.packageName))
+
+        val deleteIntent = notification.pendingTvIntent(EXTRA_DELETE_INTENT)
+        assertTrue("The notification cancel intent must have action $ACTION_CLOSE") {
+            Intent(ACTION_CLOSE).filterEquals(deleteIntent.innerIntent)
+        }
+
+        deleteIntent.send()
+        waitForWMState("The PiP app must be closed!") { state ->
+            !state.isActivityVisible(PIP_ACTIVITY)
+        }
+
+        // Also make sure the pip notification was removed
+        assertNull(notificationListener.findActivePipNotification(PIP_ACTIVITY.packageName))
+    }
+
+    /** Ensure the pip notifications reflect the title of the active media session. */
+    @Test
+    fun mediaSession_setsNotificationTitle() {
+        val mediaTitle = "\"Where has my time gone?\" - Google Interns' Choir"
+        launchPipWithMediaTitle(mediaTitle)
+
+        // ensure the launcher notification has the correct title
+        assertNotNull(notificationListener.findActivePipNotification(mediaTitle))
+        // also ensure that there is no default notification
+        assertNull(notificationListener.findActivePipNotification(PIP_ACTIVITY.packageName))
+    }
+
+    /** Ensure the pip notification can display long media titles with many characters. */
+    @Test
+    fun pipNotification_canDisplayAllChars() {
+        val az = ('a'..'z').joinToString("")
+        val AZ = ('A'..'Z').joinToString("")
+        val num = ('0'..'9').joinToString("")
+        val extra = """öäüÖÄÜß^°âêîôû!"²§³$¼%½6¬/{([)]=}?\´`¸@€+*~#'<>|µ;,·.:…-_–¯\_(ツ)_/¯"""
+        val emoji = "\uD83D\uDE00\uD83E\uDD87\uD83D\uDC00"
+        val spaces = " \t"
+
+        val title = "$emoji$spaces$az$AZ$num$extra"
+        launchPipWithMediaTitle(title)
+        assertNotNull(notificationListener.findActivePipNotification(title))
+    }
+
+    /** Ensure the notification displays app name after media session is stopped. */
+    @Test
+    fun mediaSession_revertsNotificationTitle() {
+        val title = "Hello there"
+        launchPipWithMediaTitle(title)
+        // ensure the media title is used
+        assertNotNull(notificationListener.findActivePipNotification(title))
+
+        // stop the media session
+        sendBroadcast(
+            action = PipActivity.ACTION_SET_MEDIA_TITLE,
+            boolExtras = mapOf(EXTRA_MEDIA_SESSION_ACTIVE to false)
+        )
+        // assert the notification reverted to the app name
+        assertNotNull(notificationListener.findActivePipNotification(PIP_ACTIVITY.packageName))
+    }
+
+    /** Ensure a change to the media session's title is propagated to the pip notification. */
+    @Test
+    fun mediaSession_changesNotificationTitle() {
+        val firstMediaTitle = "First Media Title"
+        launchPipWithMediaTitle(firstMediaTitle)
+
+        assertNotNull(notificationListener.findActivePipNotification(firstMediaTitle))
+
+        // now change the title
+        val secondMediaTitle = "Second Media Title"
+        sendBroadcast(
+            action = PipActivity.ACTION_SET_MEDIA_TITLE,
+            stringExtras = mapOf(EXTRA_MEDIA_SESSION_TITLE to secondMediaTitle.urlEncoded())
+        )
+        assertNotNull(notificationListener.findActivePipNotification(secondMediaTitle))
+        assertNull(notificationListener.findActivePipNotification(firstMediaTitle))
+    }
+
+    /** Enable/disable the [PipNotificationListenerService] listening to notifications. */
+    private fun toggleListenerAccess(allow: Boolean) {
+        val listenerName = PipNotificationListenerService.componentName
+        val cmd = if (allow) {
+            CMD_TEMPLATE_NOTIFICATION_ALLOW_LISTENER
+        } else {
+            CMD_TEMPLATE_NOTIFICATION_DISALLOW_LISTENER
+        }
+        executeShellCommand(cmd.format(listenerName.flattenToShortString()))
+
+        // Ensure we were successful
+        assertEquals(allow, notificationManager.isNotificationListenerAccessGranted(listenerName))
+    }
+
+    private val notificationListener: PipNotificationListenerService
+        get() = PipNotificationListenerService.instance
+            ?: error("PipNotificationListenerService not connected!")
+
+    /** Launches an app into pip mode and sets its media session title. */
+    private fun launchPipWithMediaTitle(title: String) {
+        launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP,
+            boolExtras = mapOf(EXTRA_MEDIA_SESSION_ACTIVE to true),
+            stringExtras = mapOf(EXTRA_MEDIA_SESSION_TITLE to title.urlEncoded())
+        )
+        waitForEnterPip(PIP_ACTIVITY)
+    }
+}
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/PipTestBase.kt b/tests/tests/systemui/src/android/systemui/cts/tv/PipTestBase.kt
new file mode 100644
index 0000000..6230451
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/PipTestBase.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.systemui.cts.tv
+
+import android.app.ActivityTaskManager
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.graphics.Rect
+import android.server.wm.WindowManagerState
+import android.server.wm.WindowManagerState.STATE_PAUSED
+import android.systemui.tv.cts.Components
+import android.systemui.tv.cts.Components.activityName
+import android.systemui.tv.cts.ResourceNames.STRING_PIP_MENU_BOUNDS
+import android.systemui.tv.cts.ResourceNames.SYSTEM_UI_PACKAGE
+import android.util.DisplayMetrics
+import android.util.TypedValue
+import android.view.WindowManager
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import kotlin.test.assertEquals
+
+abstract class PipTestBase : TvTestBase() {
+    protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+    protected val resources: Resources = context.resources
+    protected val windowManager: WindowManager =
+        context.getSystemService(WindowManager::class.java)
+            ?: error("Could not get a WindowManager")
+    protected val activityTaskManager: ActivityTaskManager =
+        context.getSystemService(ActivityTaskManager::class.java)
+            ?: error("Could not get an ActivityManager")
+    private val systemuiResources: Resources =
+        packageManager.getResourcesForApplication(SYSTEM_UI_PACKAGE)
+
+    /** Default timeout in milliseconds to use for wait and find operations. */
+    protected open val defaultTimeout: Long = 2_000
+
+    /** Bounds when the pip menu is open */
+    protected val menuModePipBounds: Rect = systemuiResources.run {
+        val menuBoundsId = getIdentifier(STRING_PIP_MENU_BOUNDS, "string", SYSTEM_UI_PACKAGE)
+        Rect.unflattenFromString(getString(menuBoundsId))
+            ?: error("Could not find the pip_menu_bounds resource!")
+    }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+        assumeTrue(supportsPip())
+    }
+
+    protected fun supportsPip(): Boolean =
+        packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
+
+    /** Waits until the pip animation has finished and the app is fully in pip mode. */
+    protected fun waitForEnterPip(activityName: ComponentName) {
+        wmState.waitForWithAmState("checking task windowing mode") { state: WindowManagerState ->
+            state.getTaskByActivity(activityName)?.let { task ->
+                task.windowingMode == WINDOWING_MODE_PINNED
+            } ?: false
+        } || error("Task ${activityName.flattenToShortString()} is not found or not pinned!")
+
+        wmState
+            .waitForWithAmState("checking activity windowing mode") { state: WindowManagerState ->
+                state.getTaskByActivity(activityName)?.getActivity(activityName)?.let { activity ->
+                    activity.windowingMode == WINDOWING_MODE_PINNED &&
+                        activity.state == STATE_PAUSED
+                } ?: false
+            } || error("Activity ${activityName.flattenToShortString()} is not found," +
+                " not pinned or not paused!")
+    }
+
+    /** Waits until the app is in fullscreen accounting for a possible pip transition animation. */
+    protected fun waitForFullscreen(activityName: ComponentName) {
+        wmState
+            .waitForWithAmState("checking activity windowing mode") { state: WindowManagerState ->
+                state.getTaskByActivity(activityName)?.getActivity(activityName)?.let { activity ->
+                    activity.windowingMode != WINDOWING_MODE_PINNED
+                } ?: false
+            } || error("Task ${activityName.flattenToShortString()} is not found or pinned!")
+
+        wmState.waitForWithAmState("checking task windowing mode") { state: WindowManagerState ->
+            state.getTaskByActivity(activityName)?.let { task ->
+                task.windowingMode != WINDOWING_MODE_PINNED
+            } ?: false
+        } || error("Activity ${activityName.flattenToShortString()} is not found or pinned!")
+    }
+
+    /** Waits until the given window state condition is true. Throws on timeout. */
+    protected fun waitForWMState(message: String, condition: (WindowManagerState) -> Boolean) {
+        wmState.waitFor(message, condition) || error("Timed out while waiting for $message")
+    }
+
+    /** Ensure the pip detail menu is open. */
+    protected fun assertPipMenuOpen() {
+        waitForFullscreen(Components.PIP_MENU_ACTIVITY)
+        wmState.assertActivityDisplayed(Components.PIP_MENU_ACTIVITY)
+        assertEquals(
+            expected = Components.PIP_MENU_ACTIVITY.activityName(),
+            actual = wmState.focusedActivity,
+            message = "The PiP Menu activity must be focused!"
+        )
+    }
+
+    /** Locate an object by its resource id or throw. */
+    protected fun locateByResourceName(resourceName: String): UiObject2 =
+        uiDevice.wait(Until.findObject(By.res(resourceName)), defaultTimeout)
+            ?: error("Could not locate $resourceName")
+
+    /** @return the number of pixels for a given dip value. */
+    protected fun dipToPx(dpValue: Int, dm: DisplayMetrics): Int {
+        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue.toFloat(), dm).toInt()
+    }
+}
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/TvTestBase.kt b/tests/tests/systemui/src/android/systemui/cts/tv/TvTestBase.kt
new file mode 100644
index 0000000..2759ead
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/TvTestBase.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.systemui.cts.tv
+
+import android.Manifest.permission.FORCE_STOP_PACKAGES
+import android.app.ActivityManager
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.server.wm.UiDeviceUtils
+import android.server.wm.WindowManagerStateHelper
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.Assume
+import org.junit.Before
+import java.io.IOException
+
+abstract class TvTestBase {
+    companion object {
+        private const val TAG = "TvTestBase"
+    }
+
+    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    protected val context: Context = instrumentation.context
+    protected val packageManager: PackageManager = context.packageManager
+            ?: error("Could not get a PackageManager")
+    protected val activityManager: ActivityManager =
+            context.getSystemService(ActivityManager::class.java)
+                    ?: error("Could not get a ActivityManager")
+    protected val wmState: WindowManagerStateHelper = WindowManagerStateHelper()
+
+    @Before
+    open fun setUp() {
+        Assume.assumeTrue(isTelevision())
+        UiDeviceUtils.pressWakeupButton()
+        UiDeviceUtils.pressUnlockButton()
+    }
+
+    private fun isTelevision(): Boolean =
+            packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) ||
+                    packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)
+
+    protected fun launchActivity(
+        activity: ComponentName? = null,
+        action: String? = null,
+        flags: Set<Int> = setOf(),
+        boolExtras: Map<String, Boolean> = mapOf(),
+        intExtras: Map<String, Int> = mapOf(),
+        stringExtras: Map<String, String> = mapOf()
+    ) {
+        require(activity != null || !action.isNullOrBlank()) {
+            "Cannot launch an activity with neither activity name nor action!"
+        }
+        val command = composeAmShellCommand(
+                "start", activity, action, flags, boolExtras, intExtras, stringExtras)
+        executeShellCommand(command)
+    }
+
+    protected fun startForegroundService(
+        service: ComponentName,
+        action: String? = null
+    ) {
+        val command = composeAmShellCommand("start-foreground-service", service, action)
+        executeShellCommand(command)
+    }
+
+    protected fun sendBroadcast(
+        action: String,
+        flags: Set<Int> = setOf(),
+        boolExtras: Map<String, Boolean> = mapOf(),
+        intExtras: Map<String, Int> = mapOf(),
+        stringExtras: Map<String, String> = mapOf()
+    ) {
+        val command = composeAmShellCommand(
+                "broadcast", null, action, flags, boolExtras, intExtras, stringExtras)
+        executeShellCommand(command)
+    }
+
+    protected fun stopPackage(packageName: String) {
+        SystemUtil.runWithShellPermissionIdentity({
+            activityManager.forceStopPackage(packageName)
+        }, FORCE_STOP_PACKAGES)
+    }
+
+    private fun composeAmShellCommand(
+        command: String,
+        component: ComponentName?,
+        action: String? = null,
+        flags: Set<Int> = setOf(),
+        boolExtras: Map<String, Boolean> = mapOf(),
+        intExtras: Map<String, Int> = mapOf(),
+        stringExtras: Map<String, String> = mapOf()
+    ): String = buildString {
+        append("am ")
+        append(command)
+        component?.let {
+            append(" -n ")
+            append(it.flattenToShortString())
+        }
+        action?.let {
+            append(" -a ")
+            append(it)
+        }
+        flags.forEach {
+            append(" -f ")
+            append(it)
+        }
+        boolExtras.forEach {
+            append(it.withFlag("ez"))
+        }
+        intExtras.forEach {
+            append(it.withFlag("ei"))
+        }
+        stringExtras.forEach {
+            append(it.withFlag("es"))
+        }
+    }
+
+    private fun Map.Entry<String, *>.withFlag(flag: String): String = " --$flag $key $value"
+
+    protected fun executeShellCommand(cmd: String): String {
+        try {
+            return SystemUtil.runShellCommand(instrumentation, cmd)
+        } catch (e: IOException) {
+            Log.e(TAG, "Error running shell command: $cmd")
+            throw e
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/telecom/AndroidManifest.xml b/tests/tests/telecom/AndroidManifest.xml
index 5e6a6ee..6a327d9 100644
--- a/tests/tests/telecom/AndroidManifest.xml
+++ b/tests/tests/telecom/AndroidManifest.xml
@@ -15,143 +15,154 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telecom.cts"
-    android:sharedUserId="android.telecom.cts">
-    <uses-sdk android:minSdkVersion="21" />
-    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
-    <uses-permission android:name="android.permission.CALL_PHONE" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
-    <uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.READ_CALL_LOG" />
-    <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
-    <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
-    <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED" />
+     package="android.telecom.cts"
+     android:sharedUserId="android.telecom.cts">
+    <uses-sdk android:minSdkVersion="21"/>
+    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>
+    <uses-permission android:name="android.permission.CALL_PHONE"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
+    <uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER"/>
+    <uses-permission android:name="android.permission.ACCEPT_HANDOVER"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+    <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
 
-    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
+    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name="android.telecom.cts.CtsRemoteConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.CtsConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.CtsSelfManagedConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.MockInCallService"
-            android:permission="android.permission.BIND_INCALL_SERVICE" >
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
-            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
-            <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS" android:value="true" />
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI"
+                 android:value="true"/>
+            <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS"
+                 android:value="true"/>
         </service>
 
         <service android:name="android.telecom.cts.MockCallScreeningService"
-            android:permission="android.permission.BIND_SCREENING_SERVICE"
-            android:enabled="false" >
+             android:permission="android.permission.BIND_SCREENING_SERVICE"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.CallScreeningService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.CtsPhoneAccountSuggestionService"
-                 android:permission="android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"
-                 android:enabled="false" >
+             android:permission="android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.PhoneAccountSuggestionService"/>
             </intent-filter>
         </service>
 
         <service android:name="com.android.compatibility.common.util.BlockedNumberService"
-            android:exported="true"
-            android:singleUser="true" >
+             android:exported="true"
+             android:singleUser="true">
             <intent-filter>
                 <action android:name="android.telecom.cts.InsertBlockedNumber"/>
                 <action android:name="android.telecom.cts.DeleteBlockedNumber"/>
             </intent-filter>
         </service>
 
-        <receiver android:name="android.telecom.cts.MockMissedCallNotificationReceiver">
+        <receiver android:name="android.telecom.cts.MockMissedCallNotificationReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
+                <action android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION"/>
             </intent-filter>
         </receiver>
 
-        <receiver android:name="android.telecom.cts.MockPhoneAccountChangedReceiver">
+        <receiver android:name="android.telecom.cts.MockPhoneAccountChangedReceiver"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED"/>
                 <action android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED"/>
             </intent-filter>
         </receiver>
 
-        <receiver android:name="android.telecom.cts.NewOutgoingCallBroadcastReceiver">
+        <receiver android:name="android.telecom.cts.NewOutgoingCallBroadcastReceiver"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
             </intent-filter>
         </receiver>
 
-        <activity android:name="android.telecom.cts.MockDialerActivity">
+        <activity android:name="android.telecom.cts.MockDialerActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.telecom.cts"
-                     android:label="CTS tests for android.telecom package">
+         android:targetPackage="android.telecom.cts"
+         android:label="CTS tests for android.telecom package">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/telecom/AndroidTest.xml b/tests/tests/telecom/AndroidTest.xml
index 741a897..449e8cf 100644
--- a/tests/tests/telecom/AndroidTest.xml
+++ b/tests/tests/telecom/AndroidTest.xml
@@ -37,4 +37,8 @@
         <option name="package" value="android.telecom.cts" />
         <option name="runtime-hint" value="10m20s" />
     </test>
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.TestFailureModuleController">
+        <option name="bugreportz-on-failure" value="true" />
+    </object>
 </configuration>
diff --git a/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
index 715e8f7..ab1115e 100644
--- a/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
+++ b/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
@@ -15,34 +15,34 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telecom.cts.api29incallservice"
-          android:versionCode="1"
-          android:versionName="1.0"
-          android:sharedUserId="android.telecom.cts">
+     package="android.telecom.cts.api29incallservice"
+     android:versionCode="1"
+     android:versionName="1.0"
+     android:sharedUserId="android.telecom.cts">
 
     <!-- sdk 15 is the max for read call log -->
     <uses-sdk android:minSdkVersion="15"
-              android:targetSdkVersion="29" />
+         android:targetSdkVersion="29"/>
 
     <uses-permission android:name="android.permission.READ_CALL_LOG"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
-    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
 
     <application android:label="Api29CTSInCallService">
         <service android:name=".CtsApi29InCallService"
-                 android:permission="android.permission.BIND_INCALL_SERVICE"
-                 android:launchMode="singleInstance"
-                 android:exported="true">
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:launchMode="singleInstance"
+             android:exported="true">
             <!--  indicates it's a non-UI service, required by non-Ui InCallService -->
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
         </service>
         <service android:name=".CtsApi29InCallServiceControl"
-                 android:launchMode="singleInstance">
+             android:launchMode="singleInstance"
+             android:exported="true">
             <intent-filter>
-                <action
-                    android:name="android.telecom.cts.api29incallservice.ACTION_API29_CONTROL"/>
+                <action android:name="android.telecom.cts.api29incallservice.ACTION_API29_CONTROL"/>
             </intent-filter>
         </service>
     </application>
diff --git a/tests/tests/telecom/CallRedirectionServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/CallRedirectionServiceTestApp/AndroidManifest.xml
index 8527c9e..a4b0a97 100644
--- a/tests/tests/telecom/CallRedirectionServiceTestApp/AndroidManifest.xml
+++ b/tests/tests/telecom/CallRedirectionServiceTestApp/AndroidManifest.xml
@@ -15,18 +15,20 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telecom.cts.redirectiontestapp">
+     package="android.telecom.cts.redirectiontestapp">
     <application android:label="CTSCRTest">
         <service android:name=".CtsCallRedirectionService"
-                 android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
+             android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.CallRedirectionService" />
+                <action android:name="android.telecom.CallRedirectionService"/>
             </intent-filter>
         </service>
-        <service android:name=".CtsCallRedirectionServiceController">
+        <service android:name=".CtsCallRedirectionServiceController"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.cts.redirectiontestapp.ACTION_CONTROL_CALL_REDIRECTION_SERVICE" />
+                <action android:name="android.telecom.cts.redirectiontestapp.ACTION_CONTROL_CALL_REDIRECTION_SERVICE"/>
             </intent-filter>
         </service>
     </application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/tests/telecom/CallScreeningServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/CallScreeningServiceTestApp/AndroidManifest.xml
index 1d10377..183e269 100644
--- a/tests/tests/telecom/CallScreeningServiceTestApp/AndroidManifest.xml
+++ b/tests/tests/telecom/CallScreeningServiceTestApp/AndroidManifest.xml
@@ -15,27 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telecom.cts.screeningtestapp">
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" />
-    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
+     package="android.telecom.cts.screeningtestapp">
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS"/>
+    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"/>
     <application android:label="CTSCSTest">
         <service android:name=".CtsCallScreeningService"
-                 android:permission="android.permission.BIND_SCREENING_SERVICE">
+             android:permission="android.permission.BIND_SCREENING_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.CallScreeningService" />
+                <action android:name="android.telecom.CallScreeningService"/>
             </intent-filter>
         </service>
-        <service android:name=".CallScreeningServiceControl">
+        <service android:name=".CallScreeningServiceControl"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.cts.screeningtestapp.ACTION_CONTROL_CALL_SCREENING_SERVICE" />
+                <action android:name="android.telecom.cts.screeningtestapp.ACTION_CONTROL_CALL_SCREENING_SERVICE"/>
             </intent-filter>
         </service>
         <activity android:name=".CtsPostCallActivity"
-                  android:label="CtsPostCallActivity">
+             android:label="CtsPostCallActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.action.POST_CALL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.telecom.action.POST_CALL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/telecom/ThirdPtyInCallServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/AndroidManifest.xml
index eb509ec..910feaa 100644
--- a/tests/tests/telecom/ThirdPtyInCallServiceTestApp/AndroidManifest.xml
+++ b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/AndroidManifest.xml
@@ -15,36 +15,36 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telecom.cts.thirdptyincallservice"
-          android:versionCode="1"
-          android:versionName="1.0" >
+     package="android.telecom.cts.thirdptyincallservice"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <!-- sdk 15 is the max for read call log -->
-    <uses-sdk android:minSdkVersion="15" />
+    <uses-sdk android:minSdkVersion="15"/>
 
     <uses-permission android:name="android.permission.READ_CALL_LOG"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
-    <uses-permission android:name="android.permission.CALL_COMPANION_APP" />
+    <uses-permission android:name="android.permission.CALL_COMPANION_APP"/>
 
     <application android:label="ThirdPtyCTSInCallService">
         <service android:name=".CtsThirdPartyInCallService"
-                 android:permission="android.permission.BIND_INCALL_SERVICE"
-                 android:launchMode="singleInstance"
-                 android:exported="true">
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:launchMode="singleInstance"
+             android:exported="true">
             <!--  indicates it's a non-UI service, required by non-Ui InCallService -->
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
             <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
-                       android:value="true" />
+                 android:value="true"/>
             <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS"
-                       android:value="true" />
+                 android:value="true"/>
         </service>
         <service android:name=".CtsThirdPartyInCallServiceControl"
-                 android:launchMode="singleInstance">
+             android:launchMode="singleInstance"
+             android:exported="true">
             <intent-filter>
-                <action
-                    android:name="android.telecom.cts.thirdptyincallservice.ACTION_THIRDPTY_CTRL"/>
+                <action android:name="android.telecom.cts.thirdptyincallservice.ACTION_THIRDPTY_CTRL"/>
             </intent-filter>
         </service>
     </application>
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index d0cb113..459ff27 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -61,9 +61,11 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Random;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
@@ -125,6 +127,9 @@
         List<Pair<Integer, String>> mCallStates = new ArrayList<>();
         EmergencyNumber mLastOutgoingEmergencyNumber;
 
+        LinkedBlockingQueue<Map<Integer, List<EmergencyNumber>>> mEmergencyNumberListQueue =
+               new LinkedBlockingQueue<>(2);
+
         @Override
         public void onCallStateChanged(int state, String number) {
             Log.i(TAG, "onCallStateChanged: state=" + state + ", number=" + number);
@@ -133,11 +138,24 @@
         }
 
         @Override
-        public void onOutgoingEmergencyCall(EmergencyNumber emergencyNumber) {
+        public void onOutgoingEmergencyCall(EmergencyNumber emergencyNumber, int subscriptionId) {
             Log.i(TAG, "onOutgoingEmergencyCall: emergencyNumber=" + emergencyNumber);
             mLastOutgoingEmergencyNumber = emergencyNumber;
             mCallbackSemaphore.release();
         }
+
+        @Override
+        public void onEmergencyNumberListChanged(
+                Map<Integer, List<EmergencyNumber>> emergencyNumberList) {
+            Log.i(TAG, "onEmergencyNumberChanged, total size=" + emergencyNumberList.values()
+                    .stream().mapToInt(List::size).sum());
+            mEmergencyNumberListQueue.offer(emergencyNumberList);
+        }
+
+        public Map<Integer, List<EmergencyNumber>> waitForEmergencyNumberListUpdate(
+                long timeoutMillis) throws Throwable {
+            return mEmergencyNumberListQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+        }
     }
 
     boolean mShouldTestTelecom = true;
@@ -176,8 +194,9 @@
                 mPhoneStateListener = new TestPhoneStateListener();
                 ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
                     (tm) -> tm.listen(mPhoneStateListener,
-                        PhoneStateListener.LISTEN_CALL_STATE | PhoneStateListener
-                            .LISTEN_OUTGOING_EMERGENCY_CALL));
+                        PhoneStateListener.LISTEN_CALL_STATE
+                                | PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL
+                                | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST));
                 registeredLatch.countDown();
             }
         });
diff --git a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
index 0c5b3d3..5a75fbc 100644
--- a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
@@ -29,9 +29,13 @@
 import android.telecom.TelecomManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
 
 import com.android.compatibility.common.util.SystemUtil;
 
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -118,14 +122,45 @@
         assertNotAudioRoute(mInCallCallbacks.getService(), CallAudioState.ROUTE_SPEAKER);
     }
 
-    public void testPhoneStateListenerInvokedOnOutgoingEmergencyCall() throws Exception {
+    public void testPhoneStateListenerInvokedOnOutgoingEmergencyCall() throws Throwable {
         if (!mShouldTestTelecom) {
             return;
         }
         TestUtils.setSystemDialerOverride(getInstrumentation());
+        TestUtils.setTestEmergencyPhoneAccountPackageFilter(getInstrumentation(), mContext);
         TestUtils.addTestEmergencyNumber(getInstrumentation(), TEST_EMERGENCY_NUMBER);
-        mTelecomManager.placeCall(Uri.fromParts("tel", TEST_EMERGENCY_NUMBER, null), null);
-        verifyPhoneStateListenerCallbacksForEmergencyCall(TEST_EMERGENCY_NUMBER);
+        Map<Integer, List<EmergencyNumber>> emergencyNumbers = null;
+
+        for (int i = 0; i < 5; i++) {
+            emergencyNumbers = mPhoneStateListener.waitForEmergencyNumberListUpdate(
+                    TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+            assertNotNull("Never got an update that the test emergency number was registered",
+                    emergencyNumbers);
+            if (doesEmergencyNumberListContainTestNumber(emergencyNumbers)) {
+                break;
+            }
+        }
+        assertTrue("Emergency number list from telephony still doesn't have the test number",
+                doesEmergencyNumberListContainTestNumber(emergencyNumbers));
+
+        try {
+            Bundle extras = new Bundle();
+            extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                    TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
+            mTelecomManager.placeCall(Uri.fromParts("tel", TEST_EMERGENCY_NUMBER, null), extras);
+
+            verifyPhoneStateListenerCallbacksForEmergencyCall(TEST_EMERGENCY_NUMBER);
+        } finally {
+            TestUtils.removeTestEmergencyNumber(getInstrumentation(), TEST_EMERGENCY_NUMBER);
+            TestUtils.clearTestEmergencyPhoneAccountPackageFilter(getInstrumentation());
+        }
+    }
+
+    private boolean doesEmergencyNumberListContainTestNumber(
+            Map<Integer, List<EmergencyNumber>> emergencyNumbers) {
+        return emergencyNumbers.values().stream().flatMap(List::stream)
+                .anyMatch(numberObj ->
+                        Objects.equals(numberObj.getNumber(), TEST_EMERGENCY_NUMBER));
     }
 
     public void testPhoneStateListenerInvokedOnOutgoingCall() throws Exception {
diff --git a/tests/tests/telecom2/AndroidManifest.xml b/tests/tests/telecom2/AndroidManifest.xml
index 00e58eb..d579152 100644
--- a/tests/tests/telecom2/AndroidManifest.xml
+++ b/tests/tests/telecom2/AndroidManifest.xml
@@ -15,69 +15,71 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telecom2.cts">
-    <uses-sdk android:minSdkVersion="21" />
+     package="android.telecom2.cts">
+    <uses-sdk android:minSdkVersion="21"/>
 
     <!--
-        This app contains tests to verify Telecom's behavior when the app is missing certain
-        permissions.
-    -->
+                This app contains tests to verify Telecom's behavior when the app is missing certain
+                permissions.
+            -->
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name="android.telecom.cts.MockConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.MockInCallService"
-            android:permission="android.permission.BIND_INCALL_SERVICE" >
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
         </service>
 
-        <activity android:name="android.telecom.cts.MockDialerActivity">
+        <activity android:name="android.telecom.cts.MockDialerActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.telecom2.cts"
-                     android:label="CTS tests for android.telecom package">
+         android:targetPackage="android.telecom2.cts"
+         android:label="CTS tests for android.telecom package">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/telecom2/TEST_MAPPING b/tests/tests/telecom2/TEST_MAPPING
new file mode 100644
index 0000000..b7f7d80
--- /dev/null
+++ b/tests/tests/telecom2/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTelecomTestCases2"
+    }
+  ]
+}
diff --git a/tests/tests/telecom3/AndroidManifest.xml b/tests/tests/telecom3/AndroidManifest.xml
index c606dcb..351260c 100644
--- a/tests/tests/telecom3/AndroidManifest.xml
+++ b/tests/tests/telecom3/AndroidManifest.xml
@@ -15,73 +15,78 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telecom3.cts">
-    <uses-sdk android:minSdkVersion="25" />
-    <uses-permission android:name="android.permission.CALL_PHONE" />>
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
-    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
+     package="android.telecom3.cts">
+    <uses-sdk android:minSdkVersion="25"/>
+    <uses-permission android:name="android.permission.CALL_PHONE"/>&gt;
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER"/>
+    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name="android.telecom.cts.CtsSelfManagedConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.SelfManagedAwareInCallService"
-            android:permission="android.permission.BIND_INCALL_SERVICE" >
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
-            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
-            <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS" android:value="true" />
-            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS" android:value="true" />
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI"
+                 android:value="true"/>
+            <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS"
+                 android:value="true"/>
+            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+                 android:value="true"/>
         </service>
 
-         <activity android:name="android.telecom.cts.MockDialerActivity">
+         <activity android:name="android.telecom.cts.MockDialerActivity"
+              android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.telecom3.cts"
-                     android:label="CTS tests for android.telecom package">
+         android:targetPackage="android.telecom3.cts"
+         android:label="CTS tests for android.telecom package">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/telephony/TestSmsApp/AndroidManifest.xml b/tests/tests/telephony/TestSmsApp/AndroidManifest.xml
index 210a6ee..717437e 100644
--- a/tests/tests/telephony/TestSmsApp/AndroidManifest.xml
+++ b/tests/tests/telephony/TestSmsApp/AndroidManifest.xml
@@ -15,60 +15,62 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telephony.cts.sms">
+     package="android.telephony.cts.sms">
 
     <uses-permission android:name="android.permission.READ_SMS"/>
 
     <application android:label="TestSmsApp">
-        <activity
-            android:name="android.telephony.cts.sms.MainActivity"
-            android:exported="true"/>
+        <activity android:name="android.telephony.cts.sms.MainActivity"
+             android:exported="true"/>
 
         <!-- BroadcastReceiver that listens for incoming SMS messages -->
         <receiver android:name=".SmsReceiver"
-                  android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <!-- BroadcastReceiver that listens for incoming MMS messages -->
         <receiver android:name=".MmsReceiver"
-                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
         </receiver>
 
         <!-- Activity that allows the user to send new SMS/MMS messages -->
-        <activity android:name=".ComposeSmsActivity" >
+        <activity android:name=".ComposeSmsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </activity>
 
-        <!-- Service that delivers messages from the phone "quick response" -->
+        <!-- Service that delivers messages from the phone "quick response"
+             -->
         <service android:name=".HeadlessSmsSendService"
-                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
-                 android:exported="true" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
 
     </application>
 </manifest>
-
diff --git a/tests/tests/telephony/TestSmsApp22/AndroidManifest.xml b/tests/tests/telephony/TestSmsApp22/AndroidManifest.xml
index de0047d..0413ffc 100644
--- a/tests/tests/telephony/TestSmsApp22/AndroidManifest.xml
+++ b/tests/tests/telephony/TestSmsApp22/AndroidManifest.xml
@@ -15,62 +15,64 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telephony.cts.sms23">
+     package="android.telephony.cts.sms23">
 
     <uses-sdk android:targetSdkVersion="22"/>
 
     <uses-permission android:name="android.permission.READ_SMS"/>
 
     <application android:label="TestSmsApp">
-        <activity
-            android:name="android.telephony.cts.sms23.MainActivity"
-            android:exported="true"/>
+        <activity android:name="android.telephony.cts.sms23.MainActivity"
+             android:exported="true"/>
 
         <!-- BroadcastReceiver that listens for incoming SMS messages -->
         <receiver android:name=".SmsReceiver"
-                  android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <!-- BroadcastReceiver that listens for incoming MMS messages -->
         <receiver android:name=".MmsReceiver"
-                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
         </receiver>
 
         <!-- Activity that allows the user to send new SMS/MMS messages -->
-        <activity android:name=".ComposeSmsActivity" >
+        <activity android:name=".ComposeSmsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </activity>
 
-        <!-- Service that delivers messages from the phone "quick response" -->
+        <!-- Service that delivers messages from the phone "quick response"
+             -->
         <service android:name=".HeadlessSmsSendService"
-                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
-                 android:exported="true" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
 
     </application>
 </manifest>
-
diff --git a/tests/tests/telephony/TestSmsRetrieverApp/AndroidManifest.xml b/tests/tests/telephony/TestSmsRetrieverApp/AndroidManifest.xml
index 2ac0079f..3b58119 100644
--- a/tests/tests/telephony/TestSmsRetrieverApp/AndroidManifest.xml
+++ b/tests/tests/telephony/TestSmsRetrieverApp/AndroidManifest.xml
@@ -13,16 +13,17 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telephony.cts.smsretriever">
+     package="android.telephony.cts.smsretriever">
 
     <application android:label="TestSmsRetrieverApp">
-        <activity
-            android:name="android.telephony.cts.smsretriever.MainActivity"
-            android:exported="true"/>
-	<receiver android:name="android.telephony.cts.smsretriever.SmsRetrieverBroadcastReceiver">
+        <activity android:name="android.telephony.cts.smsretriever.MainActivity"
+             android:exported="true"/>
+	<receiver android:name="android.telephony.cts.smsretriever.SmsRetrieverBroadcastReceiver"
+    	 android:exported="true">
             <intent-filter>
-                <action android:name="android.telephony.cts.action.SMS_RETRIEVED"></action>
+                <action android:name="android.telephony.cts.action.SMS_RETRIEVED"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/tests/tests/telephony/current/Android.bp b/tests/tests/telephony/current/Android.bp
index 1d4e2cf..e13ffc0 100644
--- a/tests/tests/telephony/current/Android.bp
+++ b/tests/tests/telephony/current/Android.bp
@@ -32,6 +32,7 @@
         "telephony-common",
         "android.test.runner",
         "android.test.base",
+        "framework-telephony-stubs",
         "voip-common",
     ],
     // uncomment when EuiccService tests do not use hidden APIs (Binder instances)
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index f6b655e..e8b41f0 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -15,108 +15,109 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telephony.cts">
+     package="android.telephony.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.USE_SIP" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <uses-permission android:name="android.permission.USE_SIP"/>
     <uses-permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"/>
     <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
 
     <permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"
-                android:protectionLevel="signature"/>
+         android:protectionLevel="signature"/>
     <application>
         <provider android:name="android.telephony.cts.MmsPduProvider"
-                  android:authorities="telephonyctstest"
-                  android:grantUriPermissions="true" />
+             android:authorities="telephonyctstest"
+             android:grantUriPermissions="true"/>
 
         <!-- SmsReceiver, MmsReceiver, ComposeSmsActivity, HeadlessSmsSendService together make
-        this a valid SmsApplication (that can be set as the default SMS app). Although some of these
-        classes don't do anything, they are needed to make this a valid candidate for default SMS
-        app. -->
+                    this a valid SmsApplication (that can be set as the default SMS app). Although some of these
+                    classes don't do anything, they are needed to make this a valid candidate for default SMS
+                    app. -->
         <!-- BroadcastReceiver that listens for incoming SMS messages -->
         <receiver android:name=".SmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <!-- BroadcastReceiver that listens for incoming MMS messages -->
         <receiver android:name=".MmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
         </receiver>
 
         <!-- Activity that allows the user to send new SMS/MMS messages -->
-        <activity android:name=".ComposeSmsActivity" >
+        <activity android:name=".ComposeSmsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </activity>
 
-        <!-- Service that delivers messages from the phone "quick response" -->
+        <!-- Service that delivers messages from the phone "quick response"
+             -->
         <service android:name=".HeadlessSmsSendService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
-            android:exported="true" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
 
-        <service
-          android:name="android.telephony.cts.StubInCallService"
-          android:permission="android.permission.BIND_INCALL_SERVICE">
+        <service android:name="android.telephony.cts.StubInCallService"
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
-            <meta-data
-              android:name="android.telecom.IN_CALL_SERVICE_UI"
-              android:value="true"/>
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI"
+                 android:value="true"/>
         </service>
 
-        <service
-          android:name=".MockVisualVoicemailService"
-          android:permission="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
-          android:exported="true">
+        <service android:name=".MockVisualVoicemailService"
+             android:permission="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telephony.VisualVoicemailService"/>
             </intent-filter>
         </service>
 
-        <service
-            android:name=".PermissionlessVisualVoicemailService"
-            android:enabled="false"
-            android:exported="true">
+        <service android:name=".PermissionlessVisualVoicemailService"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telephony.VisualVoicemailService"/>
             </intent-filter>
@@ -124,61 +125,62 @@
         </service>
 
         <service android:name="com.android.compatibility.common.util.BlockedNumberService"
-                android:exported="true"
-                android:singleUser="true" >
+             android:exported="true"
+             android:singleUser="true">
             <intent-filter>
                 <action android:name="android.telecom.cts.InsertBlockedNumber"/>
                 <action android:name="android.telecom.cts.DeleteBlockedNumber"/>
             </intent-filter>
         </service>
 
-        <service
-            android:name="android.telephony.euicc.cts.MockEuiccService"
-            android:permission="android.permission.BIND_EUICC_SERVICE"
-            android:exported="true">
+        <service android:name="android.telephony.euicc.cts.MockEuiccService"
+             android:permission="android.permission.BIND_EUICC_SERVICE"
+             android:exported="true">
             <intent-filter android:priority="100">
                 <action android:name="android.service.euicc.EuiccService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telephony.ims.cts.TestImsService"
-                 android:directBootAware="true"
-                 android:persistent="true"
-                 android:permission="android.permission.BIND_IMS_SERVICE">
+             android:directBootAware="true"
+             android:persistent="true"
+             android:permission="android.permission.BIND_IMS_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telephony.ims.ImsService" />
+                <action android:name="android.telephony.ims.ImsService"/>
             </intent-filter>
         </service>
 
-        <activity android:name="android.telephony.cts.StubDialerActvity">
+        <activity android:name="android.telephony.cts.StubDialerActvity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
@@ -186,41 +188,37 @@
 
         <activity android:name="android.telephony.euicc.cts.EuiccResolutionActivity"/>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
 
         <!-- This is the receiver defined by the MBMS api. -->
-        <receiver
-            android:name="android.telephony.mbms.MbmsDownloadReceiver"
-            android:permission="android.telephony.cts.embmstestapp.CTS_DOWNLOAD_PERMISSION"
-            android:enabled="true"
-            android:exported="true">
+        <receiver android:name="android.telephony.mbms.MbmsDownloadReceiver"
+             android:permission="android.telephony.cts.embmstestapp.CTS_DOWNLOAD_PERMISSION"
+             android:enabled="true"
+             android:exported="true">
         </receiver>
 
-        <provider
-            android:name="android.telephony.mbms.MbmsTempFileProvider"
-            android:authorities="android.telephony.mbms.cts"
-            android:exported="false"
-            android:grantUriPermissions="true">
+        <provider android:name="android.telephony.mbms.MbmsTempFileProvider"
+             android:authorities="android.telephony.mbms.cts"
+             android:exported="false"
+             android:grantUriPermissions="true">
         </provider>
 
         <meta-data android:name="mbms-streaming-service-override"
-                   android:value="android.telephony.cts.embmstestapp/.CtsStreamingService"/>
+             android:value="android.telephony.cts.embmstestapp/.CtsStreamingService"/>
         <meta-data android:name="mbms-download-service-override"
-                   android:value="android.telephony.cts.embmstestapp/.CtsDownloadService"/>
+             android:value="android.telephony.cts.embmstestapp/.CtsDownloadService"/>
         <meta-data android:name="mbms-group-call-service-override"
-                   android:value="android.telephony.cts.embmstestapp/.CtsGroupCallService"/>
-        <meta-data
-            android:name="mbms-file-provider-authority"
-            android:value="android.telephony.mbms.cts"/>
+             android:value="android.telephony.cts.embmstestapp/.CtsGroupCallService"/>
+        <meta-data android:name="mbms-file-provider-authority"
+             android:value="android.telephony.mbms.cts"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.telephony.cts"
-                     android:label="CTS tests of android.telephony">
+         android:targetPackage="android.telephony.cts"
+         android:label="CTS tests of android.telephony">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/telephony/current/EmbmsMiddlewareTestApp/AndroidManifest.xml b/tests/tests/telephony/current/EmbmsMiddlewareTestApp/AndroidManifest.xml
index 0798e79..3913ae0 100644
--- a/tests/tests/telephony/current/EmbmsMiddlewareTestApp/AndroidManifest.xml
+++ b/tests/tests/telephony/current/EmbmsMiddlewareTestApp/AndroidManifest.xml
@@ -15,35 +15,37 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.telephony.cts.embmstestapp">
+     package="android.telephony.cts.embmstestapp">
   <permission android:name="android.telephony.cts.embmstestapp.CTS_DOWNLOAD_PERMISSION"
-              android:protectionLevel="signature"/>
+       android:protectionLevel="signature"/>
 
   <uses-permission android:name="android.telephony.cts.embms.permission.SEND_EMBMS_INTENTS"/>
   <uses-permission android:name="android.telephony.cts.embmstestapp.CTS_DOWNLOAD_PERMISSION"/>
 
   <application android:label="EmbmsCtsMiddleware">
     <service android:name="android.telephony.cts.embmstestapp.CtsStreamingService"
-            android:launchMode="singleInstance">
+         android:launchMode="singleInstance"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.action.EmbmsStreaming" />
-        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE" />
+        <action android:name="android.telephony.action.EmbmsStreaming"/>
+        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE"/>
       </intent-filter>
     </service>
     <service android:name="android.telephony.cts.embmstestapp.CtsGroupCallService"
-             android:launchMode="singleInstance">
+         android:launchMode="singleInstance"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.action.EmbmsGroupCall" />
-        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE" />
+        <action android:name="android.telephony.action.EmbmsGroupCall"/>
+        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE"/>
       </intent-filter>
     </service>
     <service android:name="android.telephony.cts.embmstestapp.CtsDownloadService"
-             android:launchMode="singleInstance">
+         android:launchMode="singleInstance"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.action.EmbmsDownload" />
-        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE" />
+        <action android:name="android.telephony.action.EmbmsDownload"/>
+        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE"/>
       </intent-filter>
     </service>
   </application>
 </manifest>
-
diff --git a/tests/tests/telephony/current/LocationAccessingApp/AndroidManifest.xml b/tests/tests/telephony/current/LocationAccessingApp/AndroidManifest.xml
index 332d369..e6cdab8 100644
--- a/tests/tests/telephony/current/LocationAccessingApp/AndroidManifest.xml
+++ b/tests/tests/telephony/current/LocationAccessingApp/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.telephony.cts.locationaccessingapp">
+     package="android.telephony.cts.locationaccessingapp">
 
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
   <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
@@ -24,11 +24,11 @@
 
   <application android:label="LocationAccessingApp">
     <service android:name="android.telephony.cts.locationaccessingapp.CtsLocationAccessService"
-             android:launchMode="singleInstance">
+         android:launchMode="singleInstance"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.cts.locationaccessingapp.ACTION_CONTROL" />
+        <action android:name="android.telephony.cts.locationaccessingapp.ACTION_CONTROL"/>
       </intent-filter>
     </service>
   </application>
 </manifest>
-
diff --git a/tests/tests/telephony/current/LocationAccessingApp/sdk28/AndroidManifest.xml b/tests/tests/telephony/current/LocationAccessingApp/sdk28/AndroidManifest.xml
index 811d9ce..b51ee76 100644
--- a/tests/tests/telephony/current/LocationAccessingApp/sdk28/AndroidManifest.xml
+++ b/tests/tests/telephony/current/LocationAccessingApp/sdk28/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.telephony.cts.locationaccessingapp.sdk28">
+     package="android.telephony.cts.locationaccessingapp.sdk28">
 
   <uses-sdk android:targetSdkVersion="28"/>
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
@@ -24,11 +24,11 @@
 
   <application android:label="LocationAccessingAppSdk28">
     <service android:name="android.telephony.cts.locationaccessingapp.CtsLocationAccessService"
-             android:launchMode="singleInstance">
+         android:launchMode="singleInstance"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.cts.locationaccessingapp.ACTION_CONTROL" />
+        <action android:name="android.telephony.cts.locationaccessingapp.ACTION_CONTROL"/>
       </intent-filter>
     </service>
   </application>
 </manifest>
-
diff --git a/tests/tests/telephony/current/TestExternalImsServiceApp/AndroidManifest.xml b/tests/tests/telephony/current/TestExternalImsServiceApp/AndroidManifest.xml
index 7062fd4..cf733ab 100644
--- a/tests/tests/telephony/current/TestExternalImsServiceApp/AndroidManifest.xml
+++ b/tests/tests/telephony/current/TestExternalImsServiceApp/AndroidManifest.xml
@@ -16,17 +16,18 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.telephony.cts.externalimsservice">
+     package="android.telephony.cts.externalimsservice">
 
     <application>
         <service android:name=".TestExternalImsService"
-                 android:directBootAware="true"
-                 android:persistent="true">
-            <meta-data android:name="override_bind_check" android:value="true" />
+             android:directBootAware="true"
+             android:persistent="true"
+             android:exported="true">
+            <meta-data android:name="override_bind_check"
+                 android:value="true"/>
             <intent-filter>
-                <action android:name="android.telephony.ims.ImsService" />
+                <action android:name="android.telephony.ims.ImsService"/>
             </intent-filter>
         </service>
     </application>
 </manifest>
-
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
index 1ec1c81..96985cd 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
@@ -16,6 +16,9 @@
 
 package android.telephony.cts;
 
+import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_DO_FALLBACK;
+import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.net.InetAddresses;
@@ -47,6 +50,7 @@
             Arrays.asList(InetAddresses.parseNumericAddress("22.33.44.55"));
     private static final int MTU_V4 = 1440;
     private static final int MTU_V6 = 1400;
+    private static final int HANDOVER_FAILURE_MODE = HANDOVER_FAILURE_MODE_DO_FALLBACK;
 
     @Test
     public void testConstructorAndGetters() {
@@ -63,6 +67,7 @@
                 .setPcscfAddresses(PCSCFS)
                 .setMtuV4(MTU_V4)
                 .setMtuV6(MTU_V6)
+                .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .build();
 
         assertThat(response.getCause()).isEqualTo(CAUSE);
@@ -77,6 +82,7 @@
         assertThat(response.getPcscfAddresses()).isEqualTo(PCSCFS);
         assertThat(response.getMtuV4()).isEqualTo(MTU_V4);
         assertThat(response.getMtuV6()).isEqualTo(MTU_V6);
+        assertThat(response.getHandoverFailureMode()).isEqualTo(HANDOVER_FAILURE_MODE_DO_FALLBACK);
     }
 
     @Test
@@ -94,6 +100,7 @@
                 .setPcscfAddresses(PCSCFS)
                 .setMtuV4(MTU_V4)
                 .setMtuV6(MTU_V6)
+                .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .build();
 
         DataCallResponse equalsResponse = new DataCallResponse.Builder()
@@ -109,6 +116,7 @@
                 .setPcscfAddresses(PCSCFS)
                 .setMtuV4(MTU_V4)
                 .setMtuV6(MTU_V6)
+                .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .build();
 
         assertThat(response).isEqualTo(equalsResponse);
@@ -129,6 +137,7 @@
                 .setPcscfAddresses(PCSCFS)
                 .setMtuV4(MTU_V4)
                 .setMtuV6(MTU_V6)
+                .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .build();
 
         DataCallResponse notEqualsResponse = new DataCallResponse.Builder()
@@ -144,6 +153,7 @@
                 .setPcscfAddresses(PCSCFS)
                 .setMtuV4(1441)
                 .setMtuV6(1440)
+                .setHandoverFailureMode(HANDOVER_FAILURE_MODE_LEGACY)
                 .build();
 
         assertThat(response).isNotEqualTo(notEqualsResponse);
@@ -166,6 +176,7 @@
                 .setPcscfAddresses(PCSCFS)
                 .setMtuV4(MTU_V4)
                 .setMtuV6(MTU_V6)
+                .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .build();
 
         Parcel stateParcel = Parcel.obtain();
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
index c80337c..312732e 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
@@ -23,7 +23,6 @@
 import static org.junit.Assert.assertFalse;
 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 android.content.Context;
@@ -38,16 +37,19 @@
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.PhoneStateListener;
+import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -60,6 +62,8 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 public class PhoneStateListenerTest {
 
@@ -91,6 +95,7 @@
     private boolean mSecurityExceptionThrown;
     private boolean mOnRegistrationFailedCalled;
     private boolean mOnTelephonyDisplayInfoChanged;
+    private boolean mOnPhysicalChannelConfigurationCalled;
     @RadioPowerState private int mRadioPowerState;
     @SimActivationState private int mVoiceActivationState;
     private BarringInfo mBarringInfo;
@@ -957,16 +962,17 @@
         TelephonyUtils.addTestEmergencyNumber(
                 InstrumentationRegistry.getInstrumentation(), TEST_EMERGENCY_NUMBER);
 
-        assertNull(mOnOutgoingSmsEmergencyNumberChanged);
+        LinkedBlockingQueue<Pair<EmergencyNumber, Integer>> smsCallbackQueue =
+                new LinkedBlockingQueue<>(1);
 
         mHandler.post(() -> {
             mListener = new PhoneStateListener() {
                 @Override
-                public void onOutgoingEmergencySms(EmergencyNumber emergencyNumber) {
+                public void onOutgoingEmergencySms(EmergencyNumber emergencyNumber,
+                        int subscriptionId) {
                     synchronized (mLock) {
                         Log.i(TAG, "onOutgoingEmergencySms: emergencyNumber=" + emergencyNumber);
-                        mOnOutgoingSmsEmergencyNumberChanged = emergencyNumber;
-                        mLock.notify();
+                        smsCallbackQueue.offer(Pair.create(emergencyNumber, subscriptionId));
                     }
                 }
             };
@@ -978,11 +984,12 @@
         });
 
         try {
-            synchronized (mLock) {
-                if (mOnOutgoingSmsEmergencyNumberChanged == null) {
-                    mLock.wait(WAIT_TIME);
-                }
-            }
+            Pair<EmergencyNumber, Integer> emergencySmsInfo =
+                    smsCallbackQueue.poll(WAIT_TIME, TimeUnit.MILLISECONDS);
+            assertNotNull("Never got emergency sms callback", emergencySmsInfo);
+            assertEquals(TEST_EMERGENCY_NUMBER, emergencySmsInfo.first.getNumber());
+            assertEquals(SubscriptionManager.getDefaultSmsSubscriptionId(),
+                    emergencySmsInfo.second.intValue());
         } catch (InterruptedException e) {
             Log.e(TAG, "Operation interrupted.");
         } finally {
@@ -990,8 +997,6 @@
                     InstrumentationRegistry.getInstrumentation(), TEST_EMERGENCY_NUMBER);
         }
 
-        assertNotNull(mOnOutgoingSmsEmergencyNumberChanged);
-        assertEquals(mOnOutgoingSmsEmergencyNumberChanged.getNumber(), TEST_EMERGENCY_NUMBER);
     }
 
     @Test
@@ -1165,4 +1170,36 @@
         // failure because unlike other PSL registrants, this one is not called upon registration.
         assertFalse(mOnRegistrationFailedCalled);
     }
+
+    @Test
+    public void testOnPhysicalChannelConfigurationChanged() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        assertFalse(mOnPhysicalChannelConfigurationCalled);
+        mHandler.post(() -> {
+            mListener = new PhoneStateListener() {
+                @Override
+                public void onPhysicalChannelConfigurationChanged(
+                        List<PhysicalChannelConfig> configs) {
+                    synchronized (mLock) {
+                        mOnPhysicalChannelConfigurationCalled = true;
+                        mLock.notify();
+                    }
+                }
+            };
+            mTelephonyManager.listen(PhoneStateListener.LISTEN_PHYSICAL_CHANNEL_CONFIGURATION,
+                    mListener);
+        });
+
+        synchronized (mLock) {
+            while(!mOnPhysicalChannelConfigurationCalled){
+                mLock.wait(WAIT_TIME);
+            }
+        }
+
+        assertTrue(mOnPhysicalChannelConfigurationCalled);
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
index e6f30a2..8761739 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -289,10 +289,12 @@
         init();
         if (addMessageId) {
             long fakeMessageId = 19812L;
-            sendTextMessageWithMessageId(mDestAddr, mDestAddr, mSentIntent, mDeliveredIntent,
-                    fakeMessageId);
+            sendTextMessageWithMessageId(mDestAddr,
+                    String.valueOf(SystemClock.elapsedRealtimeNanos()), mSentIntent,
+                    mDeliveredIntent, fakeMessageId);
         } else {
-            sendTextMessage(mDestAddr, mDestAddr, mSentIntent, mDeliveredIntent);
+            sendTextMessage(mDestAddr, String.valueOf(SystemClock.elapsedRealtimeNanos()),
+                    mSentIntent, mDeliveredIntent);
         }
         assertTrue("[RERUN] Could not send SMS. Check signal.",
                 mSendReceiver.waitForCalls(1, TIME_OUT));
@@ -387,7 +389,8 @@
 
         // single-part SMS blocking
         init();
-        sendTextMessage(mDestAddr, mDestAddr, mSentIntent, mDeliveredIntent);
+        sendTextMessage(mDestAddr, String.valueOf(SystemClock.elapsedRealtimeNanos()),
+                mSentIntent, mDeliveredIntent);
         assertTrue("[RERUN] Could not send SMS. Check signal.",
                 mSendReceiver.waitForCalls(1, TIME_OUT));
         assertTrue("Expected no messages to be received due to number blocking.",
@@ -921,7 +924,7 @@
             if (mAction.equals(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)) {
                 sMessageId = intent.getLongExtra("messageId", 0L);
             }
-            Log.i(TAG, "onReceive " + intent.getAction());
+            Log.i(TAG, "onReceive " + intent.getAction() + " mAction " + mAction);
             if (intent.getAction().equals(mAction)) {
                 synchronized (mLock) {
                     mCalls += 1;
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
index dbb769d..ddc1677 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
@@ -35,6 +35,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.Arrays;
+
 public class SmsMessageTest {
 
     private TelephonyManager mTelephonyManager;
@@ -418,7 +420,13 @@
                 -62, -32, 48};
 
         assertArrayEquals(expectedGsmMsg, gsmMsg);
-        assertArrayEquals(expectedCdmaMsg, cdmaMsg);
+        // In CDMA, the message byte array is affected by the messageId generated by
+        // {@link com.android.internal.telephony.cdma.SmsMessage#getNextMessageId()}
+        // which is not consistent. Skip the 2 bytes which are affected by it.
+        assertArrayEquals(Arrays.copyOfRange(expectedCdmaMsg, 0, 35),
+                Arrays.copyOfRange(cdmaMsg, 0, 35));
+        assertArrayEquals(Arrays.copyOfRange(expectedCdmaMsg, 37, expectedCdmaMsg.length),
+                Arrays.copyOfRange(cdmaMsg, 37, expectedCdmaMsg.length));
     }
 
     @Test
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
index c1b67c1..87aaf3a 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -22,6 +22,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -52,6 +53,7 @@
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.AvailableNetworkInfo;
 import android.telephony.CallAttributes;
+import android.telephony.CallForwardingInfo;
 import android.telephony.CallQuality;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellLocation;
@@ -95,8 +97,10 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -123,6 +127,7 @@
     private String mSelfCertHash;
 
     private static final int TOLERANCE = 1000;
+    private static final int TIMEOUT_FOR_NETWORK_OPS = TOLERANCE * 10;
     private PhoneStateListener mListener;
     private static ConnectivityManager mCm;
     private static final String TAG = "TelephonyManagerTest";
@@ -566,12 +571,8 @@
                 TelephonyManager::getAndUpdateDefaultRespondViaMessageApplication);
     }
 
-    /**
-     * Due to the corresponding API is hidden in R and will be public in S, this test
-     * is commented and will be un-commented in Android S.
-     *
     @Test
-    public void testGetCallForwarding() {
+    public void testGetCallForwarding() throws Exception {
         List<Integer> callForwardingReasons = new ArrayList<>();
         callForwardingReasons.add(CallForwardingInfo.REASON_UNCONDITIONAL);
         callForwardingReasons.add(CallForwardingInfo.REASON_BUSY);
@@ -580,35 +581,57 @@
         callForwardingReasons.add(CallForwardingInfo.REASON_ALL);
         callForwardingReasons.add(CallForwardingInfo.REASON_ALL_CONDITIONAL);
 
-        Set<Integer> callForwardingStatus = new HashSet<Integer>();
-        callForwardingStatus.add(CallForwardingInfo.STATUS_INACTIVE);
-        callForwardingStatus.add(CallForwardingInfo.STATUS_ACTIVE);
-        callForwardingStatus.add(CallForwardingInfo.STATUS_FDN_CHECK_FAILURE);
-        callForwardingStatus.add(CallForwardingInfo.STATUS_UNKNOWN_ERROR);
-        callForwardingStatus.add(CallForwardingInfo.STATUS_NOT_SUPPORTED);
+        Set<Integer> callForwardingErrors = new HashSet<Integer>();
+        callForwardingErrors.add(TelephonyManager.CallForwardingInfoCallback
+                .RESULT_ERROR_FDN_CHECK_FAILURE);
+        callForwardingErrors.add(TelephonyManager.CallForwardingInfoCallback.RESULT_ERROR_UNKNOWN);
+        callForwardingErrors.add(TelephonyManager.CallForwardingInfoCallback
+                .RESULT_ERROR_NOT_SUPPORTED);
 
         for (int callForwardingReasonToGet : callForwardingReasons) {
             Log.d(TAG, "[testGetCallForwarding] callForwardingReasonToGet: "
                     + callForwardingReasonToGet);
-            CallForwardingInfo callForwardingInfo =
-                    ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-                            (tm) -> tm.getCallForwarding(callForwardingReasonToGet));
+            AtomicReference<CallForwardingInfo> receivedForwardingInfo = new AtomicReference<>();
+            AtomicReference<Integer> receivedErrorCode = new AtomicReference<>();
+            CountDownLatch latch = new CountDownLatch(1);
+            TelephonyManager.CallForwardingInfoCallback callback =
+                    new TelephonyManager.CallForwardingInfoCallback() {
+                        @Override
+                        public void onCallForwardingInfoAvailable(CallForwardingInfo info) {
+                            receivedForwardingInfo.set(info);
+                            latch.countDown();
+                        }
 
-            assertNotNull(callForwardingInfo);
-            assertTrue(callForwardingStatus.contains(callForwardingInfo.getStatus()));
-            assertTrue(callForwardingReasons.contains(callForwardingInfo.getReason()));
-            callForwardingInfo.getNumber();
-            assertTrue(callForwardingInfo.getTimeoutSeconds() >= 0);
+                        @Override
+                        public void onError(int error) {
+                            receivedErrorCode.set(error);
+                            latch.countDown();
+                        }
+            };
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    (tm) -> tm.getCallForwarding(callForwardingReasonToGet,
+                            getContext().getMainExecutor(), callback));
+
+            assertTrue(latch.await(TIMEOUT_FOR_NETWORK_OPS, TimeUnit.MILLISECONDS));
+            // Make sure only one of the callbacks gets invoked
+            assertTrue((receivedForwardingInfo.get() != null) ^ (receivedErrorCode.get() != null));
+            if (receivedForwardingInfo.get() != null) {
+                CallForwardingInfo info = receivedForwardingInfo.get();
+                assertTrue(callForwardingReasons.contains(info.getReason()));
+                if (info.isEnabled()) {
+                    assertNotNull(info.getNumber());
+                    assertTrue(info.getTimeoutSeconds() >= 0);
+                }
+            }
+
+            if (receivedErrorCode.get() != null) {
+                assertTrue(callForwardingErrors.contains(receivedErrorCode.get()));
+            }
         }
     }
-     */
 
-    /**
-     * Due to the corresponding API is hidden in R and will be public in S, this test
-     * is commented and will be un-commented in Android S.
-     *
     @Test
-    public void testSetCallForwarding() {
+    public void testSetCallForwarding() throws Exception {
         List<Integer> callForwardingReasons = new ArrayList<>();
         callForwardingReasons.add(CallForwardingInfo.REASON_UNCONDITIONAL);
         callForwardingReasons.add(CallForwardingInfo.REASON_BUSY);
@@ -619,68 +642,99 @@
 
         // Enable Call Forwarding
         for (int callForwardingReasonToEnable : callForwardingReasons) {
+            CountDownLatch latch = new CountDownLatch(1);
+            // Disregard success or failure; just make sure it reports back.
+            Consumer<Integer> ignoringResultListener = (x) -> latch.countDown();
+
             final CallForwardingInfo callForwardingInfoToEnable = new CallForwardingInfo(
-                    CallForwardingInfo.STATUS_ACTIVE,
+                    true,
                     callForwardingReasonToEnable,
                     TEST_FORWARD_NUMBER,
                     // time seconds
                     1);
-            Log.d(TAG, "[testSetCallForwarding] Enable Call Forwarding. Status: "
-                    + CallForwardingInfo.STATUS_ACTIVE + " Reason: "
+            Log.d(TAG, "[testSetCallForwarding] Enable Call Forwarding. Reason: "
                     + callForwardingReasonToEnable + " Number: " + TEST_FORWARD_NUMBER
                     + " Time Seconds: 1");
-            ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-                    (tm) -> tm.setCallForwarding(callForwardingInfoToEnable));
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    (tm) -> tm.setCallForwarding(callForwardingInfoToEnable,
+                            getContext().getMainExecutor(), ignoringResultListener));
+            // TODO: this takes way too long on a real network (upwards of 40s).
+            // assertTrue("No response for forwarding for reason " + callForwardingReasonToEnable,
+            //        latch.await(TIMEOUT_FOR_NETWORK_OPS * 3, TimeUnit.MILLISECONDS));
         }
 
         // Disable Call Forwarding
         for (int callForwardingReasonToDisable : callForwardingReasons) {
+            CountDownLatch latch = new CountDownLatch(1);
+            // Disregard success or failure; just make sure it reports back.
+            Consumer<Integer> ignoringResultListener = (x) -> latch.countDown();
+
             final CallForwardingInfo callForwardingInfoToDisable = new CallForwardingInfo(
-                    CallForwardingInfo.STATUS_INACTIVE,
+                    false,
                     callForwardingReasonToDisable,
                     TEST_FORWARD_NUMBER,
                     // time seconds
                     1);
-            Log.d(TAG, "[testSetCallForwarding] Disable Call Forwarding. Status: "
-                    + CallForwardingInfo.STATUS_INACTIVE + " Reason: "
+            Log.d(TAG, "[testSetCallForwarding] Disable Call Forwarding. Reason: "
                     + callForwardingReasonToDisable + " Number: " + TEST_FORWARD_NUMBER
                     + " Time Seconds: 1");
-            ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-                    (tm) -> tm.setCallForwarding(callForwardingInfoToDisable));
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    (tm) -> tm.setCallForwarding(callForwardingInfoToDisable,
+                            getContext().getMainExecutor(), ignoringResultListener));
+            // TODO: this takes way too long on a real network (upwards of 40s).
+            //assertTrue("No response for forwarding for reason " + callForwardingReasonToDisable,
+            //        latch.await(TIMEOUT_FOR_NETWORK_OPS * 3, TimeUnit.MILLISECONDS));
         }
     }
-    */
 
-    /**
-     * Due to the corresponding API is hidden in R and will be public in S, this test
-     * is commented and will be un-commented in Android S.
-     *
     @Test
-    public void testGetCallWaitingStatus() {
-        Set<Integer> callWaitingStatus = new HashSet<Integer>();
-        callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_ACTIVE);
-        callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_INACTIVE);
-        callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
-        callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED);
+    public void testGetCallWaitingStatus() throws Exception {
+        Set<Integer> validCallWaitingStatuses = new HashSet<Integer>();
+        validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_ENABLED);
+        validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_DISABLED);
+        validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
+        validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED);
 
-        int status = ShellIdentityUtils.invokeMethodWithShellPermissions(
-                mTelephonyManager, (tm) -> tm.getCallWaitingStatus());
-        assertTrue(callWaitingStatus.contains(status));
+        LinkedBlockingQueue<Integer> callWaitingStatusResult = new LinkedBlockingQueue<>(1);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager, (tm) -> tm.getCallWaitingStatus(getContext().getMainExecutor(),
+                        callWaitingStatusResult::offer));
+        assertTrue(validCallWaitingStatuses.contains(
+                callWaitingStatusResult.poll(TIMEOUT_FOR_NETWORK_OPS, TimeUnit.MILLISECONDS)));
     }
-     */
 
-    /**
-     * Due to the corresponding API is hidden in R and will be public in S, this test
-     * is commented and will be un-commented in Android S.
-     *
     @Test
-    public void testSetCallWaitingStatus() {
-        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-                (tm) -> tm.setCallWaitingStatus(true));
-        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-                (tm) -> tm.setCallWaitingStatus(false));
+    public void testSetCallWaitingStatus() throws Exception {
+        Set<Integer> validCallWaitingErrors = new HashSet<Integer>();
+        validCallWaitingErrors.add(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
+        validCallWaitingErrors.add(TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED);
+        Executor executor = getContext().getMainExecutor();
+        {
+            LinkedBlockingQueue<Integer> callWaitingResult = new LinkedBlockingQueue<>(1);
+
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    (tm) -> tm.setCallWaitingEnabled(true, executor, callWaitingResult::offer));
+            Integer result = callWaitingResult.poll(TIMEOUT_FOR_NETWORK_OPS, TimeUnit.MILLISECONDS);
+            assertNotNull("Never got callback from set call waiting", result);
+            if (result != TelephonyManager.CALL_WAITING_STATUS_ENABLED) {
+                assertTrue("Call waiting callback got an invalid value: " + result,
+                        validCallWaitingErrors.contains(result));
+            }
+        }
+
+        {
+            LinkedBlockingQueue<Integer> callWaitingResult = new LinkedBlockingQueue<>(1);
+
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    (tm) -> tm.setCallWaitingEnabled(false, executor, callWaitingResult::offer));
+            Integer result = callWaitingResult.poll(TIMEOUT_FOR_NETWORK_OPS, TimeUnit.MILLISECONDS);
+            assertNotNull("Never got callback from set call waiting", result);
+            if (result != TelephonyManager.CALL_WAITING_STATUS_DISABLED) {
+                assertTrue("Call waiting callback got an invalid value: " + result,
+                        validCallWaitingErrors.contains(result));
+            }
+        }
     }
-     */
 
     @Test
     public void testGetRadioHalVersion() {
@@ -1106,6 +1160,24 @@
     }
 
     @Test
+    public void testGetServiceStateForInactiveSub() {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        int[] allSubs = mSubscriptionManager.getActiveSubscriptionIdList();
+        // generate a subscription that is valid (>0) but inactive (not part of active subId list)
+        // A simple way to do this is sum the active subIds and add 1
+        int inactiveValidSub = 1;
+        for (int sub : allSubs) {
+            inactiveValidSub += sub;
+        }
+
+        assertNull(mTelephonyManager.createForSubscriptionId(inactiveValidSub).getServiceState());
+    }
+
+    @Test
     public void testGetSimLocale() throws InterruptedException {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             Log.d(TAG,"skipping test that requires Telephony");
@@ -1246,8 +1318,6 @@
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
-        assertEquals(mTelephonyManager.getServiceState().getState(), ServiceState.STATE_IN_SERVICE);
-
         TestThread t = new TestThread(new Runnable() {
             public void run() {
                 Looper.prepare();
@@ -1723,6 +1793,7 @@
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
+        if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
 
         try {
             ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -1746,6 +1817,7 @@
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
+        if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
 
         try {
             ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -1885,6 +1957,8 @@
             Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
+        if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
+
         assertTrue(ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 (tm) -> tm.isManualNetworkSelectionAllowed()));
     }
@@ -2655,6 +2729,210 @@
                 TelephonyManager::isDataEnabled);
     }
 
+    @Test
+    public void testThermalDataEnable() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager,
+                (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_THERMAL,
+                        false));
+
+        boolean isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
+                        TelephonyManager.DATA_ENABLED_REASON_THERMAL));
+        assertFalse(isDataEnabledForReason);
+
+        boolean isDataConnectionAvailable = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataConnectionAllowed());
+        assertFalse(isDataConnectionAvailable);
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager,
+                (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_THERMAL,
+                        true));
+
+        isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
+                        TelephonyManager.DATA_ENABLED_REASON_THERMAL));
+        assertTrue(isDataEnabledForReason);
+
+        isDataConnectionAvailable = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataConnectionAllowed());
+        assertTrue(isDataConnectionAvailable);
+    }
+
+    @Test
+    public void testPolicyDataEnable() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager,
+                (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_POLICY,
+                        false));
+
+        boolean isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
+                        TelephonyManager.DATA_ENABLED_REASON_POLICY));
+        assertFalse(isDataEnabledForReason);
+
+        boolean isDataConnectionAvailable = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataConnectionAllowed());
+        assertFalse(isDataConnectionAvailable);
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager,
+                (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_POLICY,
+                        true));
+
+        isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
+                        TelephonyManager.DATA_ENABLED_REASON_POLICY));
+        assertTrue(isDataEnabledForReason);
+
+        isDataConnectionAvailable = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataConnectionAllowed());
+        assertTrue(isDataConnectionAvailable);
+    }
+
+    @Test
+    public void testCarrierDataEnable() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager,
+                (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_CARRIER,
+                        false));
+
+        waitForMs(100);
+        boolean isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
+                        TelephonyManager.DATA_ENABLED_REASON_CARRIER));
+        assertFalse(isDataEnabledForReason);
+
+        boolean isDataConnectionAvailable = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataConnectionAllowed());
+        assertFalse(isDataConnectionAvailable);
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager,
+                (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_CARRIER,
+                        true));
+
+        waitForMs(100);
+        isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
+                        TelephonyManager.DATA_ENABLED_REASON_CARRIER));
+        assertTrue(isDataEnabledForReason);
+        isDataConnectionAvailable = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataConnectionAllowed());
+        assertTrue(isDataConnectionAvailable);
+    }
+
+    @Test
+    public void testUserDataEnable() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager,
+                (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER,
+                        false));
+
+        boolean isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
+                        TelephonyManager.DATA_ENABLED_REASON_USER));
+        assertFalse(isDataEnabledForReason);
+
+        boolean isDataConnectionAvailable = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataConnectionAllowed());
+        assertFalse(isDataConnectionAvailable);
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager,
+                (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER,
+                        true));
+
+        isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
+                        TelephonyManager.DATA_ENABLED_REASON_USER));
+        assertTrue(isDataEnabledForReason);
+        isDataConnectionAvailable = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.isDataConnectionAllowed());
+        assertTrue(isDataConnectionAvailable);
+    }
+
+    @Test
+    public void testDataDuringVoiceCallPolicy() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ShellIdentityUtils.ShellPermissionMethodHelper<Boolean, TelephonyManager> getPolicyHelper =
+                (tm) -> tm.isMobileDataPolicyEnabled(
+                        TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL);
+
+        boolean allowDataDuringVoiceCall = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, getPolicyHelper);
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabledStatus(
+                        TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL,
+                        !allowDataDuringVoiceCall));
+
+        assertNotEquals(allowDataDuringVoiceCall,
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mTelephonyManager, getPolicyHelper));
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabledStatus(
+                        TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL,
+                        allowDataDuringVoiceCall));
+
+        assertEquals(allowDataDuringVoiceCall,
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mTelephonyManager, getPolicyHelper));
+    }
+
+    @Test
+    public void testAlwaysAllowMmsDataPolicy() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ShellIdentityUtils.ShellPermissionMethodHelper<Boolean, TelephonyManager> getPolicyHelper =
+                (tm) -> tm.isMobileDataPolicyEnabled(
+                        TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED);
+
+        boolean mmsAlwaysAllowed = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, getPolicyHelper);
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabledStatus(
+                        TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED,
+                        !mmsAlwaysAllowed));
+
+        assertNotEquals(mmsAlwaysAllowed,
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mTelephonyManager, getPolicyHelper));
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabledStatus(
+                        TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED,
+                        mmsAlwaysAllowed));
+
+        assertEquals(mmsAlwaysAllowed,
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mTelephonyManager, getPolicyHelper));
+    }
+
     /**
      * Validate Emergency Number address that only contains the dialable character.
      *
@@ -2865,6 +3143,18 @@
         return mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM;
     }
 
+    /**
+     * Verify that the phone is supporting the action of setForbiddenPlmn.
+     *
+     * @return whether to proceed the test
+     */
+    private boolean test() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return false;
+        }
+        return mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM;
+    }
+
     private static int makeRadioVersion(int major, int minor) {
         if (major < 0 || minor < 0) return 0;
         return major * 100 + minor;
diff --git a/tests/tests/telephony/sdk28/TEST_MAPPING b/tests/tests/telephony/sdk28/TEST_MAPPING
new file mode 100644
index 0000000..141ee9e
--- /dev/null
+++ b/tests/tests/telephony/sdk28/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTelephonySdk28TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/telephony2/TEST_MAPPING b/tests/tests/telephony2/TEST_MAPPING
new file mode 100644
index 0000000..0407667
--- /dev/null
+++ b/tests/tests/telephony2/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTelephony2TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/telephony2/src/android/telephony2/cts/NoLocationPermissionTest.java b/tests/tests/telephony2/src/android/telephony2/cts/NoLocationPermissionTest.java
new file mode 100644
index 0000000..48d1cc59
--- /dev/null
+++ b/tests/tests/telephony2/src/android/telephony2/cts/NoLocationPermissionTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.telephony2.cts;
+
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.telephony.CellInfo;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+
+@RunWith(AndroidJUnit4.class)
+public class NoLocationPermissionTest {
+
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testGetCellLocation() {
+        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        assertNotNull(telephonyManager);
+
+        try {
+            telephonyManager.getCellLocation();
+            fail("Should throw SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testGetAllCellInfo() {
+        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        assertNotNull(telephonyManager);
+
+        try {
+            telephonyManager.getAllCellInfo();
+            fail("Should throw SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testListenCellLocation() {
+        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        assertNotNull(telephonyManager);
+
+        try {
+            telephonyManager.listen(new PhoneStateListener(Runnable::run),
+                    PhoneStateListener.LISTEN_CELL_LOCATION);
+            fail("Should throw SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestCellInfoUpdate() {
+        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        assertNotNull(telephonyManager);
+
+        try {
+            telephonyManager.requestCellInfoUpdate(Runnable::run,
+                    new TelephonyManager.CellInfoCallback() {
+                        @Override
+                        public void onCellInfo(List<CellInfo> cellInfos) {
+                        }
+                    });
+            fail("Should throw SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+}
diff --git a/tests/tests/telephony3/TEST_MAPPING b/tests/tests/telephony3/TEST_MAPPING
new file mode 100644
index 0000000..2bd2449
--- /dev/null
+++ b/tests/tests/telephony3/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTelephony3TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/telephony4/TEST_MAPPING b/tests/tests/telephony4/TEST_MAPPING
new file mode 100644
index 0000000..8ec0dcf
--- /dev/null
+++ b/tests/tests/telephony4/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSimRestrictedApisTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/telephonyprovider/AndroidManifest.xml b/tests/tests/telephonyprovider/AndroidManifest.xml
index 9a9e618..e7c5e13 100755
--- a/tests/tests/telephonyprovider/AndroidManifest.xml
+++ b/tests/tests/telephonyprovider/AndroidManifest.xml
@@ -16,77 +16,77 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telephonyprovider.cts"
-    android:targetSandboxVersion="2">
+     package="android.telephonyprovider.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Required to be default SMS app -->
         <receiver android:name="android.telephonyprovider.TelephonyProviderSmsDeliverReceiver"
-                  android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
 
         </receiver>
 
         <!-- Required to be default SMS app -->
         <receiver android:name="android.telephonyprovider.TelephonyProviderWapPushDeliverReceiver"
-                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <!-- Required to be default SMS app -->
         <service android:name="android.telephonyprovider.TelephonyProviderService"
-                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
-                 android:exported="true" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
 
         <!-- Required to be default SMS app -->
-        <activity
-            android:name="android.telephonyprovider.TelephonyProviderActivity"
-            android:label="Telephony Provider CTS Test Activity"
-            android:windowSoftInputMode="stateHidden">
+        <activity android:name="android.telephonyprovider.TelephonyProviderActivity"
+             android:label="Telephony Provider CTS Test Activity"
+             android:windowSoftInputMode="stateHidden"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </activity>
 
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS telephony provider tests"
-        android:targetPackage="android.telephonyprovider.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS telephony provider tests"
+         android:targetPackage="android.telephonyprovider.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/text/Android.bp b/tests/tests/text/Android.bp
index 0a7d918..5d898c7 100644
--- a/tests/tests/text/Android.bp
+++ b/tests/tests/text/Android.bp
@@ -30,6 +30,7 @@
         "mockito-target-minus-junit4",
         "androidx.test.rules",
         "ub-uiautomator",
+        "junit-params",
     ],
 
     libs: [
diff --git a/tests/tests/text/AndroidManifest.xml b/tests/tests/text/AndroidManifest.xml
index 0e86c5f..1e1a6d9 100644
--- a/tests/tests/text/AndroidManifest.xml
+++ b/tests/tests/text/AndroidManifest.xml
@@ -16,73 +16,77 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.text.cts"
-    android:targetSandboxVersion="2">
+     package="android.text.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
 
     <application android:maxRecents="1">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.text.cts.EmojiCtsActivity"
-            android:label="AvailableIntentsActivity"
-            android:screenOrientation="nosensor"
-            android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="AvailableIntentsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.text.method.cts.KeyListenerCtsActivity"
-            android:label="KeyListenerCtsActivity"
-            android:screenOrientation="nosensor"
-            android:windowSoftInputMode="stateAlwaysHidden"/>
+             android:label="KeyListenerCtsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"/>
 
         <activity android:name="android.text.method.cts.CtsActivity"
-            android:label="CtsActivity"
-            android:screenOrientation="nosensor"
-            android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="CtsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.text.style.cts.URLSpanCtsActivity"
-            android:label="URLSpanCtsActivity"
-            android:screenOrientation="nosensor">
+             android:label="URLSpanCtsActivity"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.text.style.cts.MockURLSpanTestActivity"
-            android:label="MockURLSpanTestActivity"
-            android:launchMode="singleTask"
-            android:alwaysRetainTaskState="true"
-            android:configChanges="orientation|keyboardHidden"
-            android:screenOrientation="nosensor">
+             android:label="MockURLSpanTestActivity"
+             android:launchMode="singleTask"
+             android:alwaysRetainTaskState="true"
+             android:configChanges="orientation|keyboardHidden"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-                <data android:scheme="ctstesttext" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+                <data android:scheme="ctstesttext"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.text.cts.MockActivity" />
+        <activity android:name="android.text.cts.MockActivity"/>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.text.cts"
-                     android:label="CTS tests of android.text">
+         android:targetPackage="android.text.cts"
+         android:label="CTS tests of android.text">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/text/src/android/text/cts/HtmlTest.java b/tests/tests/text/src/android/text/cts/HtmlTest.java
index 7f785a8..e64e0b3 100644
--- a/tests/tests/text/src/android/text/cts/HtmlTest.java
+++ b/tests/tests/text/src/android/text/cts/HtmlTest.java
@@ -20,7 +20,9 @@
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
+import android.graphics.Color;
 import android.graphics.Typeface;
 import android.text.Html;
 import android.text.Layout;
@@ -41,15 +43,17 @@
 import android.text.style.UnderlineSpan;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
 public class HtmlTest {
     @Test
     public void testSingleTagOnWhileString() {
@@ -104,50 +108,104 @@
         assertEquals(expected, spanned);
     }
 
+    private static Object[] paramsForTestColor() {
+        return new Object[] {
+                new Object[] { "<font color=\"#00FF00\">something</font>", 0xFF00FF00 },
+                new Object[] { "<font color=\"navy\">NAVY</font>", 0xFF000080 },
+                // By default use the color values from android.graphics.Color instead of HTML/CSS
+                new Object[] { "<font color=\"green\">GREEN</font>", 0xFF00FF00 },
+                new Object[] { "<font color=\"gray\">GRAY</font>", 0xFF888888 },
+                new Object[] { "<font color=\"grey\">GREY</font>", 0xFF888888 },
+                new Object[] { "<font color=\"lightgray\">LIGHTGRAY</font>", 0xFFCCCCCC },
+                new Object[] { "<font color=\"lightgrey\">LIGHTGREY</font>", 0xFFCCCCCC },
+                new Object[] { "<font color=\"darkgray\">DARKGRAY</font>", 0xFF444444 },
+                new Object[] { "<font color=\"darkgrey\">DARKGREY</font>", 0xFF444444 },
+                new Object[] { "<font color=\"Black\">BLACK</font>", Color.BLACK },
+                new Object[] { "<font color=\"RED\">red</font>", Color.RED },
+                new Object[] { "<font color=\"bLUE\">blue</font>", Color.BLUE },
+                new Object[] { "<font color=\"yellow\">YELLOW</font>", Color.YELLOW },
+                new Object[] { "<font color=\"CYAN\">cyan</font>", Color.CYAN },
+                new Object[] { "<font color=\"magenta\">magenta</font>", Color.MAGENTA },
+                new Object[] { "<font color=\"AQUA\">AQUA</font>", 0xFF00FFFF },
+                new Object[] { "<font color=\"fuchsia\">FUCHSIA</font>", 0xFFFF00FF },
+                new Object[] { "<font color=\"lime\">LIME</font>", 0xFF00FF00 },
+                new Object[] { "<font color=\"maroon\">MAROON</font>", 0xFF800000 },
+                new Object[] { "<font color=\"puRPLE\">PURPLE</font>", 0xFF800080 },
+                new Object[] { "<font color=\"olive\">OLIVE</font>", 0xFF808000 },
+                new Object[] { "<font color=\"silver\">SILVER</font>", 0xFFC0C0C0 },
+                new Object[] { "<font color=\"teal\">TEAL</font>", 0xFF008080 },
+                new Object[] { "<font color=\"#FFFFFF\">white</font>", 0xFFFFFFFF },
+
+                // Note that while Color.parseColor requires 6 or 8 hex-digit colors (i.e.
+                // #RRGGBB or #AARRGGBB), Html supports 7 or less. (But in a 7 digit hex-digit
+                // color, the first is ignored.)
+                new Object[] { "<font color=\"#00FFF\">something</font>", 0xFF000FFF }, // [23]
+                new Object[] { "<font color=\"#FF\">blue</font>", 0xFF0000FF },
+                new Object[] { "<font color=\"#FFFFFFF\">7 F's</font>", Color.WHITE },
+                new Object[] { "<font color=\"#FF00FF1\">7 hexigits</font>", 0xFFF00FF1 },
+                new Object[] { "<font color=\"#7F00FF1\">7 hexigits</font>", 0xFFF00FF1 },
+
+                new Object[] { "<font color=\"0xFF0000\">red</font>", 0xFFFF0000 },
+                new Object[] { "<font color=\"0\">zero</font>", 0xFF000000 },
+                new Object[] { "<font color=\"01\">little blue</font>", 0xFF000001 },
+                new Object[] { "<font color=\"+02\">positive blue</font>", 0xFF000002 },
+                new Object[] { "<font color=\"16777215\">decimal white</font>", Color.WHITE },
+                new Object[] { "<font color=\"16777214\">almost white</font>", 0xFFFFFFFE },
+
+                // Beyond 3 bytes rolls over, in decimal, octal, or hex.
+                new Object[] { "<font color=\"16777217\">decimal roll over</font>", 0xFF000001 },
+                new Object[] { "<font color=\"0100000007\">octal roll over</font>", 0xFF000007 },
+                new Object[] { "<font color=\"0x1000002\">hex roll over</font>", 0xFF000002 },
+        };
+    }
+
     @Test
-    public void testColor() {
+    @Parameters(method = "paramsForTestColor")
+    public void testColor(String html, int expectedColor) {
         final Class<ForegroundColorSpan> type = ForegroundColorSpan.class;
 
-        Spanned s = Html.fromHtml("<font color=\"#00FF00\">something</font>");
+        Spanned s = Html.fromHtml(html);
         ForegroundColorSpan[] colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF00FF00, colors[0].getForegroundColor());
+        if (colors.length == 0) {
+            fail("Failed to create a span from " + html);
+        }
+        int actualColor = colors[0].getForegroundColor();
+        assertEquals("Wrong color for " + html + "\nexpected: 0x"
+                + Integer.toHexString(expectedColor) + "\nactual: 0x"
+                + Integer.toHexString(actualColor), expectedColor, actualColor);
+    }
 
-        s = Html.fromHtml("<font color=\"navy\">NAVY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF000080, colors[0].getForegroundColor());
+    private static Object[] paramsForTestColorInvalid() {
+        return new Object[]{
+                "<font color=\"gibberish\">something</font>",
+                "<font color=\"WHITE\">doesn't work</font>",
+                "<font color=\"0xFF000000\">alpha not supported</font>",
+                "<font color=\"#88FFFFFF\">another with alpha</font>",
+                "<font color=\"#88FFFFFF00\">too many digits</font>",
+                "<font color=\"0x88FFFFFF00\">too many digits</font>",
+                "<font color=\"08\">not octal</font>",
+                "<font color=\"#GG\">not hex</font>",
+                "<font color=\"#00FF00+\">something</font>",
+                "<font color=\"[]\">brackets</font>",
+                "<font color=\"-01\">negative blue</font>",
+                "<font color=\"4294967000\">too big decimal</font>",
+                "<font color=\"01FFFFFFFF\">too big octal</font>",
+                "<font color=\"#FFFFFFF1\">too big hex</font>",
+        };
+    }
 
-        s = Html.fromHtml("<font color=\"gibberish\">something</font>");
-        colors = s.getSpans(0, s.length(), type);
+    @Test
+    @Parameters(method = "paramsForTestColorInvalid")
+    public void testColorInvalid(String html) {
+        final Class<ForegroundColorSpan> type = ForegroundColorSpan.class;
+
+        Spanned s = Html.fromHtml(html);
+        ForegroundColorSpan[] colors = s.getSpans(0, s.length(), type);
+        if (colors.length > 0) {
+            fail("Expected 0 spans from " + html + ". Got the color 0x"
+                    + Integer.toHexString(colors[0].getForegroundColor()));
+        }
         assertEquals(0, colors.length);
-
-        // By default use the color values from android.graphics.Color instead of HTML/CSS
-        s = Html.fromHtml("<font color=\"green\">GREEN</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF00FF00, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"gray\">GRAY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF888888, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"grey\">GREY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF888888, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"lightgray\">LIGHTGRAY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFFCCCCCC, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"lightgrey\">LIGHTGREY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFFCCCCCC, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"darkgray\">DARKGRAY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF444444, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"darkgrey\">DARKGREY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF444444, colors[0].getForegroundColor());
     }
 
     @Test
diff --git a/tests/tests/text/src/android/text/cts/StyledTextShaperTest.java b/tests/tests/text/src/android/text/cts/StyledTextShaperTest.java
new file mode 100644
index 0000000..15c6628
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/StyledTextShaperTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Typeface;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextShaper;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.StyledTextShaper;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.TypefaceSpan;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StyledTextShaperTest {
+
+    @Test
+    public void shapeText_noStyle() {
+        // Setup
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(100f);
+        String text = "Hello, World.";
+
+        // Act
+        // If the text is not styled, the result should be equal to TextShaper.shapeTextRun.
+        List<PositionedGlyphs> glyphs =
+                StyledTextShaper.shapeText(text, 0, text.length(), TextDirectionHeuristics.LTR,
+                        paint);
+        PositionedGlyphs singleStyleResult =
+                TextShaper.shapeTextRun(text, 0, text.length(), 0, text.length(), 0f, 0f, false,
+                        paint);
+
+        // Asserts
+        assertThat(glyphs.size()).isEqualTo(1);
+        assertThat(glyphs.get(0)).isEqualTo(singleStyleResult);
+    }
+
+    @Test
+    public void shapeText_multiStyle() {
+        // Setup
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(100f);
+
+        SpannableString text = new SpannableString("Hello, World.");
+
+        // Act
+        // If the text is not styled, the result should be equal to TextShaper.shapeTextRun.
+        text.setSpan(new AbsoluteSizeSpan(240), 0, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        text.setSpan(new TypefaceSpan("serif"), 7, 13, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        List<PositionedGlyphs> result =
+                StyledTextShaper.shapeText(text, 0, text.length(), TextDirectionHeuristics.LTR,
+                        paint);
+
+        // Asserts
+        assertThat(result.size()).isEqualTo(2);
+        assertThat(result.get(0).getOriginX()).isEqualTo(0f);
+        assertThat(result.get(1).getOriginX()).isGreaterThan(0f);
+        // Styled text shaper doesn't support vertical layout, so Y origin is always 0
+        assertThat(result.get(0).getOriginY()).isEqualTo(0f);
+        assertThat(result.get(1).getOriginY()).isEqualTo(0f);
+
+
+        // OEM may remove serif font, so expect only when there is a serif font.
+        if (!Typeface.SERIF.equals(Typeface.DEFAULT)) {
+            // The first character should be rendered by default font, Roboto. The last character
+            // should be rendered by serif font.
+            assertThat(result.get(0).getFont(0)).isNotEqualTo(result.get(1).getFont(0));
+        }
+
+        assertThat(result.get(0).getStyle().getFontSize()).isEqualTo(240f);
+        assertThat(result.get(1).getStyle().getFontSize()).isEqualTo(100f);
+    }
+
+    // TODO(nona): Add pixel comparison tests once we have Canvas.drawGlyph APIs.
+}
diff --git a/tests/tests/text/src/android/text/cts/TextPaintTest.java b/tests/tests/text/src/android/text/cts/TextPaintTest.java
index b9e770a..d5a6ca1 100644
--- a/tests/tests/text/src/android/text/cts/TextPaintTest.java
+++ b/tests/tests/text/src/android/text/cts/TextPaintTest.java
@@ -81,4 +81,13 @@
         } catch (NullPointerException e) {
         }
     }
+
+    // b/169080922
+    public void testInfinityTextSize_doesntCrash() {
+        Paint paint = new Paint();
+        paint.setTextSize(Float.POSITIVE_INFINITY);
+
+        // Making sure following measureText is not crashing
+        paint.measureText("Hello \uD83D\uDC4B");  // Latin characters and emoji
+    }
 }
diff --git a/tests/tests/textclassifier/Android.bp b/tests/tests/textclassifier/Android.bp
index c3e0383..01cc5a0 100644
--- a/tests/tests/textclassifier/Android.bp
+++ b/tests/tests/textclassifier/Android.bp
@@ -35,4 +35,5 @@
         "src/**/*.java",
     ],
     sdk_version: "test_current",
+    min_sdk_version: "29",
 }
diff --git a/tests/tests/textclassifier/QueryTextClassifierServiceActivity/Android.bp b/tests/tests/textclassifier/QueryTextClassifierServiceActivity/Android.bp
index ccc9362..ad0156f 100644
--- a/tests/tests/textclassifier/QueryTextClassifierServiceActivity/Android.bp
+++ b/tests/tests/textclassifier/QueryTextClassifierServiceActivity/Android.bp
@@ -25,4 +25,5 @@
         "mts"
     ],
     srcs: ["src/**/*.java"],
-}
\ No newline at end of file
+    min_sdk_version: "29",
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java
new file mode 100644
index 0000000..16d4265
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.textclassifier.ConversationAction;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ConversationActionTest {
+    private static final String TEXT = "TEXT";
+    private static final float FLOAT_TOLERANCE = 0.01f;
+    private static final PendingIntent PENDING_INTENT = PendingIntent.getActivity(
+            InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
+    private static final RemoteAction REMOTE_ACTION = new RemoteAction(
+            Icon.createWithData(new byte[0], 0, 0),
+            TEXT,
+            TEXT,
+            PENDING_INTENT);
+    private static final Bundle EXTRAS = new Bundle();
+    static {
+        EXTRAS.putString(TEXT, TEXT);
+    }
+
+    @Test
+    public void testConversationAction_minimal() {
+        ConversationAction conversationAction =
+                new ConversationAction.Builder(
+                        ConversationAction.TYPE_CALL_PHONE)
+                        .build();
+
+        ConversationAction recovered =
+                parcelizeDeparcelize(conversationAction,
+                        ConversationAction.CREATOR);
+
+        assertMinimalConversationAction(conversationAction);
+        assertMinimalConversationAction(recovered);
+    }
+
+    @Test
+    public void testConversationAction_full() {
+        ConversationAction conversationAction =
+                new ConversationAction.Builder(
+                        ConversationAction.TYPE_CALL_PHONE)
+                        .setConfidenceScore(1.0f)
+                        .setTextReply(TEXT)
+                        .setAction(REMOTE_ACTION)
+                        .setExtras(EXTRAS)
+                        .build();
+
+        ConversationAction recovered =
+                parcelizeDeparcelize(conversationAction,
+                        ConversationAction.CREATOR);
+
+        assertFullConversationAction(conversationAction);
+        assertFullConversationAction(recovered);
+    }
+
+    private static void assertMinimalConversationAction(
+            ConversationAction conversationAction) {
+        assertThat(conversationAction.getAction()).isNull();
+        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(0.0f);
+        assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_CALL_PHONE);
+    }
+
+    private static void assertFullConversationAction(
+            ConversationAction conversationAction) {
+        assertThat(conversationAction.getAction().getTitle()).isEqualTo(TEXT);
+        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(1.0f);
+        assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_CALL_PHONE);
+        assertThat(conversationAction.getTextReply()).isEqualTo(TEXT);
+        assertThat(conversationAction.getExtras().keySet()).containsExactly(TEXT);
+    }
+
+    private static <T extends Parcelable> T parcelizeDeparcelize(
+            T parcelable, Parcelable.Creator<T> creator) {
+        Parcel parcel = Parcel.obtain();
+        parcelable.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return creator.createFromParcel(parcel);
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java
index 3434aba..05bdc59 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java
@@ -15,14 +15,9 @@
  */
 package android.view.textclassifier.cts;
 
-
 import static com.google.common.truth.Truth.assertThat;
 
-import android.app.PendingIntent;
 import android.app.Person;
-import android.app.RemoteAction;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -30,7 +25,6 @@
 import android.view.textclassifier.ConversationActions;
 import android.view.textclassifier.TextClassifier;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -41,9 +35,7 @@
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -53,18 +45,7 @@
     private static final Person PERSON = new Person.Builder().setKey(TEXT).build();
     private static final ZonedDateTime TIME =
             ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
-    private static final float FLOAT_TOLERANCE = 0.01f;
-
     private static final Bundle EXTRAS = new Bundle();
-    private static final PendingIntent PENDING_INTENT = PendingIntent.getActivity(
-            InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
-
-    private static final RemoteAction REMOTE_ACTION = new RemoteAction(
-            Icon.createWithData(new byte[0], 0, 0),
-            TEXT,
-            TEXT,
-            PENDING_INTENT);
-
     static {
         EXTRAS.putString(TEXT, TEXT);
     }
@@ -98,53 +79,6 @@
     }
 
     @Test
-    public void testTypeConfig_full() {
-        TextClassifier.EntityConfig typeConfig =
-                new TextClassifier.EntityConfig.Builder()
-                        .setIncludedTypes(
-                                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))
-                        .setExcludedTypes(
-                                Collections.singletonList(ConversationAction.TYPE_CALL_PHONE))
-                        .build();
-
-        TextClassifier.EntityConfig recovered =
-                parcelizeDeparcelize(typeConfig, TextClassifier.EntityConfig.CREATOR);
-
-        assertFullTypeConfig(typeConfig);
-        assertFullTypeConfig(recovered);
-    }
-
-    @Test
-    public void testTypeConfig_full_notIncludeTypesFromTextClassifier() {
-        TextClassifier.EntityConfig typeConfig =
-                new TextClassifier.EntityConfig.Builder()
-                        .includeTypesFromTextClassifier(false)
-                        .setIncludedTypes(
-                                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))
-                        .setExcludedTypes(
-                                Collections.singletonList(ConversationAction.TYPE_CALL_PHONE))
-                        .build();
-
-        TextClassifier.EntityConfig recovered =
-                parcelizeDeparcelize(typeConfig, TextClassifier.EntityConfig.CREATOR);
-
-        assertFullTypeConfig_notIncludeTypesFromTextClassifier(typeConfig);
-        assertFullTypeConfig_notIncludeTypesFromTextClassifier(recovered);
-    }
-
-    @Test
-    public void testTypeConfig_minimal() {
-        TextClassifier.EntityConfig typeConfig =
-                new TextClassifier.EntityConfig.Builder().build();
-
-        TextClassifier.EntityConfig recovered =
-                parcelizeDeparcelize(typeConfig, TextClassifier.EntityConfig.CREATOR);
-
-        assertMinimalTypeConfig(typeConfig);
-        assertMinimalTypeConfig(recovered);
-    }
-
-    @Test
     public void testRequest_minimal() {
         ConversationActions.Message message =
                 new ConversationActions.Message.Builder(PERSON)
@@ -189,39 +123,7 @@
         assertFullRequest(recovered);
     }
 
-    @Test
-    public void testConversationAction_minimal() {
-        ConversationAction conversationAction =
-                new ConversationAction.Builder(
-                        ConversationAction.TYPE_CALL_PHONE)
-                        .build();
 
-        ConversationAction recovered =
-                parcelizeDeparcelize(conversationAction,
-                        ConversationAction.CREATOR);
-
-        assertMinimalConversationAction(conversationAction);
-        assertMinimalConversationAction(recovered);
-    }
-
-    @Test
-    public void testConversationAction_full() {
-        ConversationAction conversationAction =
-                new ConversationAction.Builder(
-                        ConversationAction.TYPE_CALL_PHONE)
-                        .setConfidenceScore(1.0f)
-                        .setTextReply(TEXT)
-                        .setAction(REMOTE_ACTION)
-                        .setExtras(EXTRAS)
-                        .build();
-
-        ConversationAction recovered =
-                parcelizeDeparcelize(conversationAction,
-                        ConversationAction.CREATOR);
-
-        assertFullConversationAction(conversationAction);
-        assertFullConversationAction(recovered);
-    }
 
     @Test
     public void testConversationActions_full() {
@@ -257,7 +159,7 @@
         assertMinimalConversationActions(recovered);
     }
 
-    private void assertFullMessage(ConversationActions.Message message) {
+    private static void assertFullMessage(ConversationActions.Message message) {
         assertThat(message.getText().toString()).isEqualTo(TEXT);
         assertThat(message.getAuthor()).isEqualTo(PERSON);
         assertThat(message.getExtras().keySet()).containsExactly(TEXT);
@@ -270,45 +172,7 @@
         assertThat(message.getReferenceTime()).isNull();
     }
 
-    private void assertFullTypeConfig(TextClassifier.EntityConfig typeConfig) {
-        List<String> extraTypesFromTextClassifier = Arrays.asList(
-                ConversationAction.TYPE_CALL_PHONE,
-                ConversationAction.TYPE_CREATE_REMINDER);
-
-        Collection<String> resolvedTypes =
-                typeConfig.resolveEntityListModifications(extraTypesFromTextClassifier);
-
-        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
-        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList()))
-                .containsExactly(ConversationAction.TYPE_OPEN_URL);
-        assertThat(resolvedTypes).containsExactly(
-                ConversationAction.TYPE_OPEN_URL, ConversationAction.TYPE_CREATE_REMINDER);
-    }
-
-    private void assertFullTypeConfig_notIncludeTypesFromTextClassifier(
-            TextClassifier.EntityConfig typeConfig) {
-        List<String> extraTypesFromTextClassifier = Arrays.asList(
-                ConversationAction.TYPE_CALL_PHONE,
-                ConversationAction.TYPE_CREATE_REMINDER);
-
-        Collection<String> resolvedTypes =
-                typeConfig.resolveEntityListModifications(extraTypesFromTextClassifier);
-
-        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isFalse();
-        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList()))
-                .containsExactly(ConversationAction.TYPE_OPEN_URL);
-        assertThat(resolvedTypes).containsExactly(ConversationAction.TYPE_OPEN_URL);
-    }
-
-    private void assertMinimalTypeConfig(TextClassifier.EntityConfig typeConfig) {
-        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
-        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList())).isEmpty();
-        assertThat(typeConfig.resolveEntityListModifications(
-                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))).containsExactly(
-                ConversationAction.TYPE_OPEN_URL);
-    }
-
-    private void assertMinimalRequest(ConversationActions.Request request) {
+    private static void assertMinimalRequest(ConversationActions.Request request) {
         assertThat(request.getConversation()).hasSize(1);
         assertThat(request.getConversation().get(0).getText().toString()).isEqualTo(TEXT);
         assertThat(request.getConversation().get(0).getAuthor()).isEqualTo(PERSON);
@@ -318,7 +182,7 @@
         assertThat(request.getExtras().size()).isEqualTo(0);
     }
 
-    private void assertFullRequest(ConversationActions.Request request) {
+    private static void assertFullRequest(ConversationActions.Request request) {
         assertThat(request.getConversation()).hasSize(1);
         assertThat(request.getConversation().get(0).getText().toString()).isEqualTo(TEXT);
         assertThat(request.getConversation().get(0).getAuthor()).isEqualTo(PERSON);
@@ -328,37 +192,21 @@
         assertThat(request.getExtras().keySet()).containsExactly(TEXT);
     }
 
-    private void assertMinimalConversationAction(
-            ConversationAction conversationAction) {
-        assertThat(conversationAction.getAction()).isNull();
-        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(0.0f);
-        assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_CALL_PHONE);
-    }
-
-    private void assertFullConversationAction(
-            ConversationAction conversationAction) {
-        assertThat(conversationAction.getAction().getTitle()).isEqualTo(TEXT);
-        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(1.0f);
-        assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_CALL_PHONE);
-        assertThat(conversationAction.getTextReply()).isEqualTo(TEXT);
-        assertThat(conversationAction.getExtras().keySet()).containsExactly(TEXT);
-    }
-
-    private void assertMinimalConversationActions(ConversationActions conversationActions) {
+    private static void assertMinimalConversationActions(ConversationActions conversationActions) {
         assertThat(conversationActions.getConversationActions()).hasSize(1);
         assertThat(conversationActions.getConversationActions().get(0).getType())
                 .isEqualTo(ConversationAction.TYPE_CALL_PHONE);
         assertThat(conversationActions.getId()).isNull();
     }
 
-    private void assertFullConversationActions(ConversationActions conversationActions) {
+    private static void assertFullConversationActions(ConversationActions conversationActions) {
         assertThat(conversationActions.getConversationActions()).hasSize(1);
         assertThat(conversationActions.getConversationActions().get(0).getType())
                 .isEqualTo(ConversationAction.TYPE_CALL_PHONE);
         assertThat(conversationActions.getId()).isEqualTo(ID);
     }
 
-    private <T extends Parcelable> T parcelizeDeparcelize(
+    private static <T extends Parcelable> T parcelizeDeparcelize(
             T parcelable, Parcelable.Creator<T> creator) {
         Parcel parcel = Parcel.obtain();
         parcelable.writeToParcel(parcel, 0);
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java
new file mode 100644
index 0000000..72550c4
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.cts;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SelectionEventTest {
+
+    @Test
+    public void testSelectionEvent_placeholder() {
+        // TODO: add tests for SelectionEvent
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationContextTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationContextTest.java
new file mode 100644
index 0000000..d03f03c
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationContextTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.cts;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassificationContextTest {
+
+    @Test
+    public void testTextClassificationContext_placeholder() {
+        // TODO: add tests for TextClassificationContext
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationManagerTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
index bcc3870..c76de62 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
@@ -16,141 +16,31 @@
 
 package android.view.textclassifier.cts;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
-import android.icu.util.ULocale;
-import android.os.Bundle;
-import android.os.LocaleList;
-import android.service.textclassifier.TextClassifierService;
-import android.view.textclassifier.ConversationAction;
-import android.view.textclassifier.ConversationActions;
-import android.view.textclassifier.SelectionEvent;
-import android.view.textclassifier.TextClassification;
-import android.view.textclassifier.TextClassificationContext;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
-import android.view.textclassifier.TextClassifierEvent;
-import android.view.textclassifier.TextLanguage;
-import android.view.textclassifier.TextLinks;
-import android.view.textclassifier.TextSelection;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
-import com.google.common.collect.Range;
-
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
 
 @SmallTest
-@RunWith(Parameterized.class)
+@RunWith(AndroidJUnit4.class)
 public class TextClassificationManagerTest {
-
-    private static final String CURRENT = "current";
-    private static final String SESSION = "session";
-    private static final String DEFAULT = "default";
-    private static final String NO_OP = "no_op";
-
-    @Parameterized.Parameters(name = "{0}")
-    public static Iterable<Object> textClassifierTypes() {
-        return Arrays.asList(CURRENT, SESSION, DEFAULT, NO_OP);
-    }
-
-    @Parameterized.Parameter
-    public String mTextClassifierType;
-
-    private static final String BUNDLE_KEY = "key";
-    private static final String BUNDLE_VALUE = "value";
-    private static final Bundle BUNDLE = new Bundle();
-    static {
-        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
-    }
-    private static final LocaleList LOCALES = LocaleList.forLanguageTags("en");
-    private static final int START = 1;
-    private static final int END = 3;
-    // This text has lots of things that are probably entities in many cases.
-    private static final String TEXT = "An email address is test@example.com. A phone number"
-            + " might be +12122537077. Somebody lives at 123 Main Street, Mountain View, CA,"
-            + " and there's good stuff at https://www.android.com :)";
-    private static final TextSelection.Request TEXT_SELECTION_REQUEST =
-            new TextSelection.Request.Builder(TEXT, START, END)
-                    .setDefaultLocales(LOCALES)
-                    .build();
-    private static final TextClassification.Request TEXT_CLASSIFICATION_REQUEST =
-            new TextClassification.Request.Builder(TEXT, START, END)
-                    .setDefaultLocales(LOCALES)
-                    .build();
-    private static final TextLanguage.Request TEXT_LANGUAGE_REQUEST =
-            new TextLanguage.Request.Builder(TEXT)
-                    .setExtras(BUNDLE)
-                    .build();
-    private static final ConversationActions.Message FIRST_MESSAGE =
-            new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_SELF)
-                    .setText(TEXT)
-                    .build();
-    private static final ConversationActions.Message SECOND_MESSAGE =
-            new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_OTHERS)
-                    .setText(TEXT)
-                    .build();
-    private static final ConversationActions.Request CONVERSATION_ACTIONS_REQUEST =
-            new ConversationActions.Request.Builder(
-                    Arrays.asList(FIRST_MESSAGE, SECOND_MESSAGE)).build();
-
     private TextClassificationManager mManager;
-    private TextClassifier mClassifier;
 
     @Before
     public void setup() {
         mManager = InstrumentationRegistry.getTargetContext()
                 .getSystemService(TextClassificationManager.class);
         mManager.setTextClassifier(null); // Resets the classifier.
-        if (mTextClassifierType.equals(CURRENT)) {
-            mClassifier = mManager.getTextClassifier();
-        } else if (mTextClassifierType.equals(SESSION)) {
-            mClassifier = mManager.createTextClassificationSession(
-                    new TextClassificationContext.Builder(
-                            InstrumentationRegistry.getTargetContext().getPackageName(),
-                            TextClassifier.WIDGET_TYPE_TEXTVIEW)
-                            .build());
-        } else if (mTextClassifierType.equals(NO_OP)) {
-            mClassifier = TextClassifier.NO_OP;
-        } else {
-            mClassifier = TextClassifierService.getDefaultTextClassifierImplementation(
-                    InstrumentationRegistry.getTargetContext());
-        }
-    }
-
-    @After
-    public void tearDown() {
-        mClassifier.destroy();
-    }
-
-    @Test
-    public void testTextClassifierDestroy() {
-        mClassifier.destroy();
-        if (mTextClassifierType.equals(SESSION)) {
-            assertEquals(true, mClassifier.isDestroyed());
-        }
-    }
-
-    @Test
-    public void testGetMaxGenerateLinksTextLength() {
-        // TODO(b/143249163): Verify the value get from TextClassificationConstants
-        assertTrue(mClassifier.getMaxGenerateLinksTextLength() >= 0);
     }
 
     @Test
@@ -159,162 +49,4 @@
         mManager.setTextClassifier(classifier);
         assertEquals(classifier, mManager.getTextClassifier());
     }
-
-    @Test
-    public void testSmartSelection() {
-        assertValidResult(mClassifier.suggestSelection(TEXT_SELECTION_REQUEST));
-    }
-
-    @Test
-    public void testSuggestSelectionWith4Param() {
-        assertValidResult(mClassifier.suggestSelection(TEXT, START, END, LOCALES));
-    }
-
-    @Test
-    public void testClassifyText() {
-        assertValidResult(mClassifier.classifyText(TEXT_CLASSIFICATION_REQUEST));
-    }
-
-    @Test
-    public void testClassifyTextWith4Param() {
-        assertValidResult(mClassifier.classifyText(TEXT, START, END, LOCALES));
-    }
-
-    @Test
-    public void testGenerateLinks() {
-        assertValidResult(mClassifier.generateLinks(new TextLinks.Request.Builder(TEXT).build()));
-    }
-
-    @Test
-    public void testSuggestConversationActions() {
-        ConversationActions conversationActions =
-                mClassifier.suggestConversationActions(CONVERSATION_ACTIONS_REQUEST);
-
-        assertValidResult(conversationActions);
-    }
-
-    @Test
-    public void testResolveEntityListModifications_only_hints() {
-        TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.createWithHints(
-                Arrays.asList("some_hint"));
-        assertEquals(1, entityConfig.getHints().size());
-        assertTrue(entityConfig.getHints().contains("some_hint"));
-        assertEquals(new HashSet<String>(Arrays.asList("foo", "bar")),
-                entityConfig.resolveEntityListModifications(Arrays.asList("foo", "bar")));
-    }
-
-    @Test
-    public void testResolveEntityListModifications_include_exclude() {
-        TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.create(
-                Arrays.asList("some_hint"),
-                Arrays.asList("a", "b", "c"),
-                Arrays.asList("b", "d", "x"));
-        assertEquals(1, entityConfig.getHints().size());
-        assertTrue(entityConfig.getHints().contains("some_hint"));
-        assertEquals(new HashSet(Arrays.asList("a", "c", "w")),
-                new HashSet(entityConfig.resolveEntityListModifications(
-                        Arrays.asList("c", "w", "x"))));
-    }
-
-    @Test
-    public void testResolveEntityListModifications_explicit() {
-        TextClassifier.EntityConfig entityConfig =
-                TextClassifier.EntityConfig.createWithExplicitEntityList(Arrays.asList("a", "b"));
-        assertEquals(Collections.EMPTY_LIST, entityConfig.getHints());
-        assertEquals(new HashSet<String>(Arrays.asList("a", "b")),
-                entityConfig.resolveEntityListModifications(Arrays.asList("w", "x")));
-    }
-
-    @Test
-    public void testOnSelectionEvent() {
-        // Doesn't crash.
-        mClassifier.onSelectionEvent(
-                SelectionEvent.createSelectionStartedEvent(SelectionEvent.INVOCATION_MANUAL, 0));
-    }
-
-    @Test
-    public void testOnTextClassifierEvent() {
-        // Doesn't crash.
-        mClassifier.onTextClassifierEvent(
-                new TextClassifierEvent.ConversationActionsEvent.Builder(
-                        TextClassifierEvent.TYPE_SMART_ACTION)
-                        .build());
-    }
-
-    @Test
-    public void testLanguageDetection() {
-        assertValidResult(mClassifier.detectLanguage(TEXT_LANGUAGE_REQUEST));
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testLanguageDetection_nullRequest() {
-        assertValidResult(mClassifier.detectLanguage(null));
-    }
-
-    private static void assertValidResult(TextSelection selection) {
-        assertNotNull(selection);
-        assertTrue(selection.getSelectionStartIndex() >= 0);
-        assertTrue(selection.getSelectionEndIndex() > selection.getSelectionStartIndex());
-        assertTrue(selection.getEntityCount() >= 0);
-        for (int i = 0; i < selection.getEntityCount(); i++) {
-            final String entity = selection.getEntity(i);
-            assertNotNull(entity);
-            final float confidenceScore = selection.getConfidenceScore(entity);
-            assertTrue(confidenceScore >= 0);
-            assertTrue(confidenceScore <= 1);
-        }
-    }
-
-    private static void assertValidResult(TextClassification classification) {
-        assertNotNull(classification);
-        assertTrue(classification.getEntityCount() >= 0);
-        for (int i = 0; i < classification.getEntityCount(); i++) {
-            final String entity = classification.getEntity(i);
-            assertNotNull(entity);
-            final float confidenceScore = classification.getConfidenceScore(entity);
-            assertTrue(confidenceScore >= 0);
-            assertTrue(confidenceScore <= 1);
-        }
-        assertNotNull(classification.getActions());
-    }
-
-    private static void assertValidResult(TextLinks links) {
-        assertNotNull(links);
-        for (TextLinks.TextLink link : links.getLinks()) {
-            assertTrue(link.getEntityCount() > 0);
-            assertTrue(link.getStart() >= 0);
-            assertTrue(link.getStart() <= link.getEnd());
-            for (int i = 0; i < link.getEntityCount(); i++) {
-                String entityType = link.getEntity(i);
-                assertNotNull(entityType);
-                final float confidenceScore = link.getConfidenceScore(entityType);
-                assertTrue(confidenceScore >= 0);
-                assertTrue(confidenceScore <= 1);
-            }
-        }
-    }
-
-    private static void assertValidResult(TextLanguage language) {
-        assertNotNull(language);
-        assertNotNull(language.getExtras());
-        assertTrue(language.getLocaleHypothesisCount() >= 0);
-        for (int i = 0; i < language.getLocaleHypothesisCount(); i++) {
-            final ULocale locale = language.getLocale(i);
-            assertNotNull(locale);
-            final float confidenceScore = language.getConfidenceScore(locale);
-            assertTrue(confidenceScore >= 0);
-            assertTrue(confidenceScore <= 1);
-        }
-    }
-
-    private static void assertValidResult(ConversationActions conversationActions) {
-        assertNotNull(conversationActions);
-        List<ConversationAction> conversationActionsList =
-                conversationActions.getConversationActions();
-        assertNotNull(conversationActionsList);
-        for (ConversationAction conversationAction : conversationActionsList) {
-            assertThat(conversationAction.getType()).isNotNull();
-            assertThat(conversationAction.getConfidenceScore()).isIn(Range.closed(0f, 1.0f));
-        }
-    }
 }
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
new file mode 100644
index 0000000..77e9a4a
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.view.View;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassifier;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassificationTest {
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+
+    private static final double ACCEPTED_DELTA = 0.0000001;
+    private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
+    private static final int START = 5;
+    private static final int END = 20;
+    private static final String ID = "id123";
+    private static final LocaleList LOCALES = LocaleList.forLanguageTags("fr,en,de,es");
+
+    @Test
+    public void testTextClassification() {
+        final float addressScore = 0.1f;
+        final float emailScore = 0.9f;
+        final PendingIntent intent1 = PendingIntent.getActivity(
+                InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
+        final String label1 = "label1";
+        final String description1 = "description1";
+        final Icon icon1 = generateTestIcon(16, 16, Color.RED);
+        final PendingIntent intent2 = PendingIntent.getActivity(
+                InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
+        final String label2 = "label2";
+        final String description2 = "description2";
+        final Icon icon2 = generateTestIcon(16, 16, Color.GREEN);
+
+        final TextClassification classification = new TextClassification.Builder()
+                .setText(TEXT)
+                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
+                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
+                .addAction(new RemoteAction(icon1, label1, description1, intent1))
+                .addAction(new RemoteAction(icon2, label2, description2, intent2))
+                .setId(ID)
+                .setExtras(BUNDLE)
+                .build();
+
+        assertEquals(TEXT, classification.getText());
+        assertEquals(2, classification.getEntityCount());
+        assertEquals(TextClassifier.TYPE_EMAIL, classification.getEntity(0));
+        assertEquals(TextClassifier.TYPE_ADDRESS, classification.getEntity(1));
+        assertEquals(addressScore, classification.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+        assertEquals(emailScore, classification.getConfidenceScore(TextClassifier.TYPE_EMAIL),
+                ACCEPTED_DELTA);
+        assertEquals(0, classification.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+
+        // Legacy API
+        assertNull(classification.getIntent());
+        assertNull(classification.getLabel());
+        assertNull(classification.getIcon());
+        assertNull(classification.getOnClickListener());
+
+        assertEquals(2, classification.getActions().size());
+        assertEquals(label1, classification.getActions().get(0).getTitle());
+        assertEquals(description1, classification.getActions().get(0).getContentDescription());
+        assertEquals(intent1, classification.getActions().get(0).getActionIntent());
+        assertNotNull(classification.getActions().get(0).getIcon());
+        assertEquals(label2, classification.getActions().get(1).getTitle());
+        assertEquals(description2, classification.getActions().get(1).getContentDescription());
+        assertEquals(intent2, classification.getActions().get(1).getActionIntent());
+        assertNotNull(classification.getActions().get(1).getIcon());
+        assertEquals(ID, classification.getId());
+        assertEquals(BUNDLE_VALUE, classification.getExtras().getString(BUNDLE_KEY));
+    }
+
+    @Test
+    public void testTextClassificationLegacy() {
+        final float addressScore = 0.1f;
+        final float emailScore = 0.9f;
+        final Intent intent = new Intent();
+        final String label = "label";
+        final Drawable icon = new ColorDrawable(Color.RED);
+        final View.OnClickListener onClick = v -> {
+        };
+
+        final TextClassification classification = new TextClassification.Builder()
+                .setText(TEXT)
+                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
+                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
+                .setIntent(intent)
+                .setLabel(label)
+                .setIcon(icon)
+                .setOnClickListener(onClick)
+                .setId(ID)
+                .build();
+
+        assertEquals(TEXT, classification.getText());
+        assertEquals(2, classification.getEntityCount());
+        assertEquals(TextClassifier.TYPE_EMAIL, classification.getEntity(0));
+        assertEquals(TextClassifier.TYPE_ADDRESS, classification.getEntity(1));
+        assertEquals(addressScore, classification.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+        assertEquals(emailScore, classification.getConfidenceScore(TextClassifier.TYPE_EMAIL),
+                ACCEPTED_DELTA);
+        assertEquals(0, classification.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+
+        assertEquals(intent, classification.getIntent());
+        assertEquals(label, classification.getLabel());
+        assertEquals(icon, classification.getIcon());
+        assertEquals(onClick, classification.getOnClickListener());
+        assertEquals(ID, classification.getId());
+    }
+
+    @Test
+    public void testTextClassification_defaultValues() {
+        final TextClassification classification = new TextClassification.Builder().build();
+
+        assertEquals(null, classification.getText());
+        assertEquals(0, classification.getEntityCount());
+        assertEquals(null, classification.getIntent());
+        assertEquals(null, classification.getLabel());
+        assertEquals(null, classification.getIcon());
+        assertEquals(null, classification.getOnClickListener());
+        assertEquals(0, classification.getActions().size());
+        assertNull(classification.getId());
+        assertTrue(classification.getExtras().isEmpty());
+    }
+
+    @Test
+    public void testTextClassificationRequest() {
+        final TextClassification.Request request =
+                new TextClassification.Request.Builder(TEXT, START, END)
+                        .setDefaultLocales(LOCALES)
+                        .setExtras(BUNDLE)
+                        .build();
+
+        assertEquals(LOCALES, request.getDefaultLocales());
+        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
+    }
+
+    @Test
+    public void testTextClassificationRequest_nullValues() {
+        final TextClassification.Request request =
+                new TextClassification.Request.Builder(TEXT, START, END)
+                        .setDefaultLocales(null)
+                        .build();
+
+        assertNull(request.getDefaultLocales());
+        assertTrue(request.getExtras().isEmpty());
+    }
+
+    @Test
+    public void testTextClassificationRequest_defaultValues() {
+        final TextClassification.Request request =
+                new TextClassification.Request.Builder(TEXT, START, END).build();
+        assertNull(request.getDefaultLocales());
+        assertTrue(request.getExtras().isEmpty());
+    }
+
+    /** Helper to generate Icons for testing. */
+    private static Icon generateTestIcon(int width, int height, int colorValue) {
+        final int numPixels = width * height;
+        final int[] colors = new int[numPixels];
+        for (int i = 0; i < numPixels; ++i) {
+            colors[i] = colorValue;
+        }
+        final Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
+        return Icon.createWithBitmap(bitmap);
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierTest.java
new file mode 100644
index 0000000..fc481f4
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierTest.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.icu.util.ULocale;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.textclassifier.TextClassifierService;
+import android.view.textclassifier.ConversationAction;
+import android.view.textclassifier.ConversationActions;
+import android.view.textclassifier.SelectionEvent;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationContext;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextClassifierEvent;
+import android.view.textclassifier.TextLanguage;
+import android.view.textclassifier.TextLinks;
+import android.view.textclassifier.TextSelection;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.Range;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class TextClassifierTest {
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+    private static final LocaleList LOCALES = LocaleList.forLanguageTags("en");
+    private static final int START = 1;
+    private static final int END = 3;
+    // This text has lots of things that are probably entities in many cases.
+    private static final String TEXT = "An email address is test@example.com. A phone number"
+            + " might be +12122537077. Somebody lives at 123 Main Street, Mountain View, CA,"
+            + " and there's good stuff at https://www.android.com :)";
+    private static final TextSelection.Request TEXT_SELECTION_REQUEST =
+            new TextSelection.Request.Builder(TEXT, START, END)
+                    .setDefaultLocales(LOCALES)
+                    .build();
+    private static final TextClassification.Request TEXT_CLASSIFICATION_REQUEST =
+            new TextClassification.Request.Builder(TEXT, START, END)
+                    .setDefaultLocales(LOCALES)
+                    .build();
+    private static final TextLanguage.Request TEXT_LANGUAGE_REQUEST =
+            new TextLanguage.Request.Builder(TEXT)
+                    .setExtras(BUNDLE)
+                    .build();
+    private static final ConversationActions.Message FIRST_MESSAGE =
+            new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_SELF)
+                    .setText(TEXT)
+                    .build();
+    private static final ConversationActions.Message SECOND_MESSAGE =
+            new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_OTHERS)
+                    .setText(TEXT)
+                    .build();
+    private static final ConversationActions.Request CONVERSATION_ACTIONS_REQUEST =
+            new ConversationActions.Request.Builder(
+                    Arrays.asList(FIRST_MESSAGE, SECOND_MESSAGE)).build();
+
+    private static final String CURRENT = "current";
+    private static final String SESSION = "session";
+    private static final String DEFAULT = "default";
+    private static final String NO_OP = "no_op";
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Iterable<Object> textClassifierTypes() {
+        return Arrays.asList(CURRENT, SESSION, DEFAULT, NO_OP);
+    }
+
+    @Parameterized.Parameter
+    public String mTextClassifierType;
+
+    private TextClassifier mClassifier;
+
+    @Before
+    public void setup() {
+        TextClassificationManager manager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(TextClassificationManager.class);
+        manager.setTextClassifier(null); // Resets the classifier.
+        if (mTextClassifierType.equals(CURRENT)) {
+            mClassifier = manager.getTextClassifier();
+        } else if (mTextClassifierType.equals(SESSION)) {
+            mClassifier = manager.createTextClassificationSession(
+                    new TextClassificationContext.Builder(
+                            InstrumentationRegistry.getTargetContext().getPackageName(),
+                            TextClassifier.WIDGET_TYPE_TEXTVIEW)
+                            .build());
+        } else if (mTextClassifierType.equals(NO_OP)) {
+            mClassifier = TextClassifier.NO_OP;
+        } else {
+            mClassifier = TextClassifierService.getDefaultTextClassifierImplementation(
+                    InstrumentationRegistry.getTargetContext());
+        }
+    }
+
+    @After
+    public void tearDown() {
+        mClassifier.destroy();
+    }
+
+    @Test
+    public void testTextClassifierDestroy() {
+        mClassifier.destroy();
+        if (mTextClassifierType.equals(SESSION)) {
+            assertEquals(true, mClassifier.isDestroyed());
+        }
+    }
+
+    @Test
+    public void testGetMaxGenerateLinksTextLength() {
+        // TODO(b/143249163): Verify the value get from TextClassificationConstants
+        assertTrue(mClassifier.getMaxGenerateLinksTextLength() >= 0);
+    }
+
+    @Test
+    public void testSmartSelection() {
+        assertValidResult(mClassifier.suggestSelection(TEXT_SELECTION_REQUEST));
+    }
+
+    @Test
+    public void testSuggestSelectionWith4Param() {
+        assertValidResult(mClassifier.suggestSelection(TEXT, START, END, LOCALES));
+    }
+
+    @Test
+    public void testClassifyText() {
+        assertValidResult(mClassifier.classifyText(TEXT_CLASSIFICATION_REQUEST));
+    }
+
+    @Test
+    public void testClassifyTextWith4Param() {
+        assertValidResult(mClassifier.classifyText(TEXT, START, END, LOCALES));
+    }
+
+    @Test
+    public void testGenerateLinks() {
+        assertValidResult(mClassifier.generateLinks(new TextLinks.Request.Builder(TEXT).build()));
+    }
+
+    @Test
+    public void testSuggestConversationActions() {
+        ConversationActions conversationActions =
+                mClassifier.suggestConversationActions(CONVERSATION_ACTIONS_REQUEST);
+
+        assertValidResult(conversationActions);
+    }
+
+    @Test
+    public void testLanguageDetection() {
+        assertValidResult(mClassifier.detectLanguage(TEXT_LANGUAGE_REQUEST));
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testLanguageDetection_nullRequest() {
+        assertValidResult(mClassifier.detectLanguage(null));
+    }
+
+    @Test
+    public void testOnSelectionEvent() {
+        // Doesn't crash.
+        mClassifier.onSelectionEvent(
+                SelectionEvent.createSelectionStartedEvent(SelectionEvent.INVOCATION_MANUAL, 0));
+    }
+
+    @Test
+    public void testOnTextClassifierEvent() {
+        // Doesn't crash.
+        mClassifier.onTextClassifierEvent(
+                new TextClassifierEvent.ConversationActionsEvent.Builder(
+                        TextClassifierEvent.TYPE_SMART_ACTION)
+                        .build());
+    }
+
+    @Test
+    public void testResolveEntityListModifications_only_hints() {
+        TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.createWithHints(
+                Arrays.asList("some_hint"));
+        assertEquals(1, entityConfig.getHints().size());
+        assertTrue(entityConfig.getHints().contains("some_hint"));
+        assertEquals(new HashSet<String>(Arrays.asList("foo", "bar")),
+                entityConfig.resolveEntityListModifications(Arrays.asList("foo", "bar")));
+    }
+
+    @Test
+    public void testResolveEntityListModifications_include_exclude() {
+        TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.create(
+                Arrays.asList("some_hint"),
+                Arrays.asList("a", "b", "c"),
+                Arrays.asList("b", "d", "x"));
+        assertEquals(1, entityConfig.getHints().size());
+        assertTrue(entityConfig.getHints().contains("some_hint"));
+        assertEquals(new HashSet(Arrays.asList("a", "c", "w")),
+                new HashSet(entityConfig.resolveEntityListModifications(
+                        Arrays.asList("c", "w", "x"))));
+    }
+
+    @Test
+    public void testResolveEntityListModifications_explicit() {
+        TextClassifier.EntityConfig entityConfig =
+                TextClassifier.EntityConfig.createWithExplicitEntityList(Arrays.asList("a", "b"));
+        assertEquals(Collections.EMPTY_LIST, entityConfig.getHints());
+        assertEquals(new HashSet<String>(Arrays.asList("a", "b")),
+                entityConfig.resolveEntityListModifications(Arrays.asList("w", "x")));
+    }
+
+    @Test
+    public void testEntityConfig_full() {
+        TextClassifier.EntityConfig entityConfig =
+                new TextClassifier.EntityConfig.Builder()
+                        .setIncludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))
+                        .setExcludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_CALL_PHONE))
+                        .build();
+
+        TextClassifier.EntityConfig recovered =
+                parcelizeDeparcelize(entityConfig, TextClassifier.EntityConfig.CREATOR);
+
+        assertFullEntityConfig(entityConfig);
+        assertFullEntityConfig(recovered);
+    }
+
+    @Test
+    public void testEntityConfig_full_notIncludeTypesFromTextClassifier() {
+        TextClassifier.EntityConfig entityConfig =
+                new TextClassifier.EntityConfig.Builder()
+                        .includeTypesFromTextClassifier(false)
+                        .setIncludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))
+                        .setExcludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_CALL_PHONE))
+                        .build();
+
+        TextClassifier.EntityConfig recovered =
+                parcelizeDeparcelize(entityConfig, TextClassifier.EntityConfig.CREATOR);
+
+        assertFullEntityConfig_notIncludeTypesFromTextClassifier(entityConfig);
+        assertFullEntityConfig_notIncludeTypesFromTextClassifier(recovered);
+    }
+
+    @Test
+    public void testEntityConfig_minimal() {
+        TextClassifier.EntityConfig entityConfig =
+                new TextClassifier.EntityConfig.Builder().build();
+
+        TextClassifier.EntityConfig recovered =
+                parcelizeDeparcelize(entityConfig, TextClassifier.EntityConfig.CREATOR);
+
+        assertMinimalEntityConfig(entityConfig);
+        assertMinimalEntityConfig(recovered);
+    }
+
+    private static void assertValidResult(TextSelection selection) {
+        assertNotNull(selection);
+        assertTrue(selection.getSelectionStartIndex() >= 0);
+        assertTrue(selection.getSelectionEndIndex() > selection.getSelectionStartIndex());
+        assertTrue(selection.getEntityCount() >= 0);
+        for (int i = 0; i < selection.getEntityCount(); i++) {
+            final String entity = selection.getEntity(i);
+            assertNotNull(entity);
+            final float confidenceScore = selection.getConfidenceScore(entity);
+            assertTrue(confidenceScore >= 0);
+            assertTrue(confidenceScore <= 1);
+        }
+    }
+
+    private static void assertValidResult(TextClassification classification) {
+        assertNotNull(classification);
+        assertTrue(classification.getEntityCount() >= 0);
+        for (int i = 0; i < classification.getEntityCount(); i++) {
+            final String entity = classification.getEntity(i);
+            assertNotNull(entity);
+            final float confidenceScore = classification.getConfidenceScore(entity);
+            assertTrue(confidenceScore >= 0);
+            assertTrue(confidenceScore <= 1);
+        }
+        assertNotNull(classification.getActions());
+    }
+
+    private static void assertValidResult(TextLinks links) {
+        assertNotNull(links);
+        for (TextLinks.TextLink link : links.getLinks()) {
+            assertTrue(link.getEntityCount() > 0);
+            assertTrue(link.getStart() >= 0);
+            assertTrue(link.getStart() <= link.getEnd());
+            for (int i = 0; i < link.getEntityCount(); i++) {
+                String entityType = link.getEntity(i);
+                assertNotNull(entityType);
+                final float confidenceScore = link.getConfidenceScore(entityType);
+                assertTrue(confidenceScore >= 0);
+                assertTrue(confidenceScore <= 1);
+            }
+        }
+    }
+
+    private static void assertValidResult(TextLanguage language) {
+        assertNotNull(language);
+        assertNotNull(language.getExtras());
+        assertTrue(language.getLocaleHypothesisCount() >= 0);
+        for (int i = 0; i < language.getLocaleHypothesisCount(); i++) {
+            final ULocale locale = language.getLocale(i);
+            assertNotNull(locale);
+            final float confidenceScore = language.getConfidenceScore(locale);
+            assertTrue(confidenceScore >= 0);
+            assertTrue(confidenceScore <= 1);
+        }
+    }
+
+    private static void assertValidResult(ConversationActions conversationActions) {
+        assertNotNull(conversationActions);
+        List<ConversationAction> conversationActionsList =
+                conversationActions.getConversationActions();
+        assertNotNull(conversationActionsList);
+        for (ConversationAction conversationAction : conversationActionsList) {
+            assertThat(conversationAction.getType()).isNotNull();
+            assertThat(conversationAction.getConfidenceScore()).isIn(Range.closed(0f, 1.0f));
+        }
+    }
+
+    private static void assertFullEntityConfig_notIncludeTypesFromTextClassifier(
+            TextClassifier.EntityConfig typeConfig) {
+        List<String> extraTypesFromTextClassifier = Arrays.asList(
+                ConversationAction.TYPE_CALL_PHONE,
+                ConversationAction.TYPE_CREATE_REMINDER);
+
+        Collection<String> resolvedTypes =
+                typeConfig.resolveEntityListModifications(extraTypesFromTextClassifier);
+
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isFalse();
+        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList()))
+                .containsExactly(ConversationAction.TYPE_OPEN_URL);
+        assertThat(resolvedTypes).containsExactly(ConversationAction.TYPE_OPEN_URL);
+    }
+
+    private static void assertFullEntityConfig(TextClassifier.EntityConfig typeConfig) {
+        List<String> extraTypesFromTextClassifier = Arrays.asList(
+                ConversationAction.TYPE_CALL_PHONE,
+                ConversationAction.TYPE_CREATE_REMINDER);
+
+        Collection<String> resolvedTypes =
+                typeConfig.resolveEntityListModifications(extraTypesFromTextClassifier);
+
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
+        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList()))
+                .containsExactly(ConversationAction.TYPE_OPEN_URL);
+        assertThat(resolvedTypes).containsExactly(
+                ConversationAction.TYPE_OPEN_URL, ConversationAction.TYPE_CREATE_REMINDER);
+    }
+
+    private static void assertMinimalEntityConfig(TextClassifier.EntityConfig typeConfig) {
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
+        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList())).isEmpty();
+        assertThat(typeConfig.resolveEntityListModifications(
+                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))).containsExactly(
+                ConversationAction.TYPE_OPEN_URL);
+    }
+
+    private static <T extends Parcelable> T parcelizeDeparcelize(
+            T parcelable, Parcelable.Creator<T> creator) {
+        Parcel parcel = Parcel.obtain();
+        parcelable.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return creator.createFromParcel(parcel);
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
deleted file mode 100644
index 1d7ac22..0000000
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.textclassifier.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.app.PendingIntent;
-import android.app.RemoteAction;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.icu.util.ULocale;
-import android.os.Bundle;
-import android.os.LocaleList;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.style.URLSpan;
-import android.view.View;
-import android.view.textclassifier.TextClassification;
-import android.view.textclassifier.TextClassifier;
-import android.view.textclassifier.TextLanguage;
-import android.view.textclassifier.TextLinks;
-import android.view.textclassifier.TextSelection;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.google.common.collect.ImmutableMap;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * TextClassifier value objects tests.
- *
- * <p>Contains unit tests for value objects passed to/from TextClassifier APIs.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TextClassifierValueObjectsTest {
-
-    private static final float EPSILON = 0.000001f;
-    private static final String BUNDLE_KEY = "key";
-    private static final String BUNDLE_VALUE = "value";
-    private static final Bundle BUNDLE = new Bundle();
-
-    static {
-        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
-    }
-
-    private static final double ACCEPTED_DELTA = 0.0000001;
-    private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
-    private static final int START = 5;
-    private static final int END = 20;
-    private static final int ANOTHER_START = 22;
-    private static final int ANOTHER_END = 24;
-    private static final String ID = "id123";
-    private static final LocaleList LOCALES = LocaleList.forLanguageTags("fr,en,de,es");
-
-    @Test
-    public void testTextSelection() {
-        final float addressScore = 0.1f;
-        final float emailScore = 0.9f;
-
-        final TextSelection selection = new TextSelection.Builder(START, END)
-                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
-                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
-                .setId(ID)
-                .setExtras(BUNDLE)
-                .build();
-
-        assertEquals(START, selection.getSelectionStartIndex());
-        assertEquals(END, selection.getSelectionEndIndex());
-        assertEquals(2, selection.getEntityCount());
-        assertEquals(TextClassifier.TYPE_EMAIL, selection.getEntity(0));
-        assertEquals(TextClassifier.TYPE_ADDRESS, selection.getEntity(1));
-        assertEquals(addressScore, selection.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-        assertEquals(emailScore, selection.getConfidenceScore(TextClassifier.TYPE_EMAIL),
-                ACCEPTED_DELTA);
-        assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
-        assertEquals(ID, selection.getId());
-        assertEquals(BUNDLE_VALUE, selection.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextSelection_differentParams() {
-        final int start = 0;
-        final int end = 1;
-        final float confidenceScore = 0.5f;
-        final String id = "2hukwu3m3k44f1gb0";
-
-        final TextSelection selection = new TextSelection.Builder(start, end)
-                .setEntityType(TextClassifier.TYPE_URL, confidenceScore)
-                .setId(id)
-                .build();
-
-        assertEquals(start, selection.getSelectionStartIndex());
-        assertEquals(end, selection.getSelectionEndIndex());
-        assertEquals(1, selection.getEntityCount());
-        assertEquals(TextClassifier.TYPE_URL, selection.getEntity(0));
-        assertEquals(confidenceScore, selection.getConfidenceScore(TextClassifier.TYPE_URL),
-                ACCEPTED_DELTA);
-        assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
-        assertEquals(id, selection.getId());
-    }
-
-    @Test
-    public void testTextSelection_defaultValues() {
-        TextSelection selection = new TextSelection.Builder(START, END).build();
-        assertEquals(0, selection.getEntityCount());
-        assertNull(selection.getId());
-        assertTrue(selection.getExtras().isEmpty());
-    }
-
-    @Test
-    public void testTextSelection_prunedConfidenceScore() {
-        final float phoneScore = -0.1f;
-        final float prunedPhoneScore = 0f;
-        final float otherScore = 1.5f;
-        final float prunedOtherScore = 1.0f;
-
-        final TextSelection selection = new TextSelection.Builder(START, END)
-                .setEntityType(TextClassifier.TYPE_PHONE, phoneScore)
-                .setEntityType(TextClassifier.TYPE_OTHER, otherScore)
-                .build();
-
-        assertEquals(prunedPhoneScore, selection.getConfidenceScore(TextClassifier.TYPE_PHONE),
-                ACCEPTED_DELTA);
-        assertEquals(prunedOtherScore, selection.getConfidenceScore(TextClassifier.TYPE_OTHER),
-                ACCEPTED_DELTA);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testTextSelection_invalidStartParams() {
-        new TextSelection.Builder(-1 /* start */, END)
-                .build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testTextSelection_invalidEndParams() {
-        new TextSelection.Builder(START, 0 /* end */)
-                .build();
-    }
-
-    @Test(expected = IndexOutOfBoundsException.class)
-    public void testTextSelection_entityIndexOutOfBounds() {
-        final TextSelection selection = new TextSelection.Builder(START, END).build();
-        final int outOfBoundsIndex = selection.getEntityCount();
-        selection.getEntity(outOfBoundsIndex);
-    }
-
-    @Test
-    public void testTextSelectionRequest() {
-        final TextSelection.Request request = new TextSelection.Request.Builder(TEXT, START, END)
-                .setDefaultLocales(LOCALES)
-                .setExtras(BUNDLE)
-                .build();
-        assertEquals(TEXT, request.getText().toString());
-        assertEquals(START, request.getStartIndex());
-        assertEquals(END, request.getEndIndex());
-        assertEquals(LOCALES, request.getDefaultLocales());
-        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextSelectionRequest_nullValues() {
-        final TextSelection.Request request =
-                new TextSelection.Request.Builder(TEXT, START, END)
-                        .setDefaultLocales(null)
-                        .build();
-        assertNull(request.getDefaultLocales());
-    }
-
-    @Test
-    public void testTextSelectionRequest_defaultValues() {
-        final TextSelection.Request request =
-                new TextSelection.Request.Builder(TEXT, START, END).build();
-        assertNull(request.getDefaultLocales());
-        assertTrue(request.getExtras().isEmpty());
-    }
-
-    @Test
-    public void testTextClassification() {
-        final float addressScore = 0.1f;
-        final float emailScore = 0.9f;
-        final PendingIntent intent1 = PendingIntent.getActivity(
-                InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
-        final String label1 = "label1";
-        final String description1 = "description1";
-        final Icon icon1 = generateTestIcon(16, 16, Color.RED);
-        final PendingIntent intent2 = PendingIntent.getActivity(
-                InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
-        final String label2 = "label2";
-        final String description2 = "description2";
-        final Icon icon2 = generateTestIcon(16, 16, Color.GREEN);
-
-        final TextClassification classification = new TextClassification.Builder()
-                .setText(TEXT)
-                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
-                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
-                .addAction(new RemoteAction(icon1, label1, description1, intent1))
-                .addAction(new RemoteAction(icon2, label2, description2, intent2))
-                .setId(ID)
-                .setExtras(BUNDLE)
-                .build();
-
-        assertEquals(TEXT, classification.getText());
-        assertEquals(2, classification.getEntityCount());
-        assertEquals(TextClassifier.TYPE_EMAIL, classification.getEntity(0));
-        assertEquals(TextClassifier.TYPE_ADDRESS, classification.getEntity(1));
-        assertEquals(addressScore, classification.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-        assertEquals(emailScore, classification.getConfidenceScore(TextClassifier.TYPE_EMAIL),
-                ACCEPTED_DELTA);
-        assertEquals(0, classification.getConfidenceScore("random_type"), ACCEPTED_DELTA);
-
-        // Legacy API
-        assertNull(classification.getIntent());
-        assertNull(classification.getLabel());
-        assertNull(classification.getIcon());
-        assertNull(classification.getOnClickListener());
-
-        assertEquals(2, classification.getActions().size());
-        assertEquals(label1, classification.getActions().get(0).getTitle());
-        assertEquals(description1, classification.getActions().get(0).getContentDescription());
-        assertEquals(intent1, classification.getActions().get(0).getActionIntent());
-        assertNotNull(classification.getActions().get(0).getIcon());
-        assertEquals(label2, classification.getActions().get(1).getTitle());
-        assertEquals(description2, classification.getActions().get(1).getContentDescription());
-        assertEquals(intent2, classification.getActions().get(1).getActionIntent());
-        assertNotNull(classification.getActions().get(1).getIcon());
-        assertEquals(ID, classification.getId());
-        assertEquals(BUNDLE_VALUE, classification.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextClassificationLegacy() {
-        final float addressScore = 0.1f;
-        final float emailScore = 0.9f;
-        final Intent intent = new Intent();
-        final String label = "label";
-        final Drawable icon = new ColorDrawable(Color.RED);
-        final View.OnClickListener onClick = v -> {
-        };
-
-        final TextClassification classification = new TextClassification.Builder()
-                .setText(TEXT)
-                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
-                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
-                .setIntent(intent)
-                .setLabel(label)
-                .setIcon(icon)
-                .setOnClickListener(onClick)
-                .setId(ID)
-                .build();
-
-        assertEquals(TEXT, classification.getText());
-        assertEquals(2, classification.getEntityCount());
-        assertEquals(TextClassifier.TYPE_EMAIL, classification.getEntity(0));
-        assertEquals(TextClassifier.TYPE_ADDRESS, classification.getEntity(1));
-        assertEquals(addressScore, classification.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-        assertEquals(emailScore, classification.getConfidenceScore(TextClassifier.TYPE_EMAIL),
-                ACCEPTED_DELTA);
-        assertEquals(0, classification.getConfidenceScore("random_type"), ACCEPTED_DELTA);
-
-        assertEquals(intent, classification.getIntent());
-        assertEquals(label, classification.getLabel());
-        assertEquals(icon, classification.getIcon());
-        assertEquals(onClick, classification.getOnClickListener());
-        assertEquals(ID, classification.getId());
-    }
-
-    @Test
-    public void testTextClassification_defaultValues() {
-        final TextClassification classification = new TextClassification.Builder().build();
-
-        assertEquals(null, classification.getText());
-        assertEquals(0, classification.getEntityCount());
-        assertEquals(null, classification.getIntent());
-        assertEquals(null, classification.getLabel());
-        assertEquals(null, classification.getIcon());
-        assertEquals(null, classification.getOnClickListener());
-        assertEquals(0, classification.getActions().size());
-        assertNull(classification.getId());
-        assertTrue(classification.getExtras().isEmpty());
-    }
-
-    @Test
-    public void testTextClassificationRequest() {
-        final TextClassification.Request request =
-                new TextClassification.Request.Builder(TEXT, START, END)
-                        .setDefaultLocales(LOCALES)
-                        .setExtras(BUNDLE)
-                        .build();
-
-        assertEquals(LOCALES, request.getDefaultLocales());
-        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextClassificationRequest_nullValues() {
-        final TextClassification.Request request =
-                new TextClassification.Request.Builder(TEXT, START, END)
-                        .setDefaultLocales(null)
-                        .build();
-
-        assertNull(request.getDefaultLocales());
-        assertTrue(request.getExtras().isEmpty());
-    }
-
-    @Test
-    public void testTextClassificationRequest_defaultValues() {
-        final TextClassification.Request request =
-                new TextClassification.Request.Builder(TEXT, START, END).build();
-        assertNull(request.getDefaultLocales());
-        assertTrue(request.getExtras().isEmpty());
-    }
-
-    @Test
-    public void testTextLinks_defaultValues() {
-        final TextLinks textLinks = new TextLinks.Builder(TEXT).build();
-
-        assertEquals(TEXT, textLinks.getText());
-        assertTrue(textLinks.getExtras().isEmpty());
-        assertTrue(textLinks.getLinks().isEmpty());
-    }
-
-    @Test
-    public void testTextLinks_full() {
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .setExtras(BUNDLE)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_PHONE, 1.0f),
-                        BUNDLE)
-                .build();
-
-        assertEquals(TEXT, textLinks.getText());
-        assertEquals(BUNDLE_VALUE, textLinks.getExtras().getString(BUNDLE_KEY));
-        assertEquals(2, textLinks.getLinks().size());
-
-        final List<TextLinks.TextLink> resultList = new ArrayList<>(textLinks.getLinks());
-        final TextLinks.TextLink textLinkNoExtra = resultList.get(0);
-        assertEquals(TextClassifier.TYPE_ADDRESS, textLinkNoExtra.getEntity(0));
-        assertEquals(1.0f, textLinkNoExtra.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                EPSILON);
-        assertEquals(Bundle.EMPTY, textLinkNoExtra.getExtras());
-
-        final TextLinks.TextLink textLinkHasExtra = resultList.get(1);
-        assertEquals(TextClassifier.TYPE_PHONE, textLinkHasExtra.getEntity(0));
-        assertEquals(1.0f, textLinkHasExtra.getConfidenceScore(TextClassifier.TYPE_PHONE),
-                EPSILON);
-        assertEquals(BUNDLE_VALUE, textLinkHasExtra.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextLinks_clearTextLinks() {
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .setExtras(BUNDLE)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .clearTextLinks()
-                .build();
-        assertEquals(0, textLinks.getLinks().size());
-    }
-
-    @Test
-    public void testTextLinks_apply() {
-        final SpannableString spannableString = SpannableString.valueOf(TEXT);
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .addLink(ANOTHER_START, ANOTHER_END,
-                        ImmutableMap.of(TextClassifier.TYPE_PHONE, 1.0f,
-                                TextClassifier.TYPE_ADDRESS, 0.5f))
-                .build();
-
-        final int status = textLinks.apply(
-                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, null);
-        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
-                spannableString.length() - 1,
-                TextLinks.TextLinkSpan.class);
-
-        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
-        assertEquals(2, textLinkSpans.length);
-
-        final TextLinks.TextLink textLink = textLinkSpans[0].getTextLink();
-        final TextLinks.TextLink anotherTextLink = textLinkSpans[1].getTextLink();
-
-        assertEquals(START, textLink.getStart());
-        assertEquals(END, textLink.getEnd());
-        assertEquals(1, textLink.getEntityCount());
-        assertEquals(TextClassifier.TYPE_ADDRESS, textLink.getEntity(0));
-        assertEquals(1.0f, textLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-        assertEquals(ANOTHER_START, anotherTextLink.getStart());
-        assertEquals(ANOTHER_END, anotherTextLink.getEnd());
-        assertEquals(2, anotherTextLink.getEntityCount());
-        assertEquals(TextClassifier.TYPE_PHONE, anotherTextLink.getEntity(0));
-        assertEquals(1.0f, anotherTextLink.getConfidenceScore(TextClassifier.TYPE_PHONE),
-                ACCEPTED_DELTA);
-        assertEquals(TextClassifier.TYPE_ADDRESS, anotherTextLink.getEntity(1));
-        assertEquals(0.5f, anotherTextLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-    }
-
-    @Test
-    public void testTextLinks_applyStrategyReplace() {
-        final SpannableString spannableString = SpannableString.valueOf(TEXT);
-        final URLSpan urlSpan = new URLSpan("http://www.google.com");
-        spannableString.setSpan(urlSpan, START, END, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .build();
-
-        final int status = textLinks.apply(
-                spannableString, TextLinks.APPLY_STRATEGY_REPLACE, null);
-        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
-                spannableString.length() - 1,
-                TextLinks.TextLinkSpan.class);
-        final URLSpan[] urlSpans = spannableString.getSpans(0, spannableString.length() - 1,
-                URLSpan.class);
-
-        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
-        assertEquals(1, textLinkSpans.length);
-        assertEquals(0, urlSpans.length);
-    }
-
-    @Test
-    public void testTextLinks_applyStrategyIgnore() {
-        final SpannableString spannableString = SpannableString.valueOf(TEXT);
-        final URLSpan urlSpan = new URLSpan("http://www.google.com");
-        spannableString.setSpan(urlSpan, START, END, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .build();
-
-        final int status = textLinks.apply(
-                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, null);
-        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
-                spannableString.length() - 1,
-                TextLinks.TextLinkSpan.class);
-        final URLSpan[] urlSpans = spannableString.getSpans(0, spannableString.length() - 1,
-                URLSpan.class);
-
-        assertEquals(TextLinks.STATUS_NO_LINKS_APPLIED, status);
-        assertEquals(0, textLinkSpans.length);
-        assertEquals(1, urlSpans.length);
-    }
-
-    @Test
-    public void testTextLinks_applyWithCustomSpanFactory() {
-        final class CustomTextLinkSpan extends TextLinks.TextLinkSpan {
-            private CustomTextLinkSpan(TextLinks.TextLink textLink) {
-                super(textLink);
-            }
-        }
-        final SpannableString spannableString = SpannableString.valueOf(TEXT);
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .build();
-
-        final int status = textLinks.apply(
-                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, CustomTextLinkSpan::new);
-        final CustomTextLinkSpan[] customTextLinkSpans = spannableString.getSpans(0,
-                spannableString.length() - 1,
-                CustomTextLinkSpan.class);
-
-        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
-        assertEquals(1, customTextLinkSpans.length);
-
-        final TextLinks.TextLink textLink = customTextLinkSpans[0].getTextLink();
-
-        assertEquals(START, textLink.getStart());
-        assertEquals(END, textLink.getEnd());
-        assertEquals(1, textLink.getEntityCount());
-        assertEquals(TextClassifier.TYPE_ADDRESS, textLink.getEntity(0));
-        assertEquals(1.0f, textLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-    }
-
-    @Test
-    public void testTextLinksRequest_defaultValues() {
-        final TextLinks.Request request = new TextLinks.Request.Builder(TEXT).build();
-
-        assertEquals(TEXT, request.getText());
-        assertNull(request.getDefaultLocales());
-        assertTrue(request.getExtras().isEmpty());
-        assertNull(request.getEntityConfig());
-    }
-
-    @Test
-    public void testTextLinksRequest_full() {
-        final ZonedDateTime referenceTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(1000L),
-                ZoneId.of("UTC"));
-        final TextLinks.Request request = new TextLinks.Request.Builder(TEXT)
-                .setDefaultLocales(LOCALES)
-                .setExtras(BUNDLE)
-                .setEntityConfig(TextClassifier.EntityConfig.createWithHints(
-                        Collections.singletonList(TextClassifier.HINT_TEXT_IS_EDITABLE)))
-                .setReferenceTime(referenceTime)
-                .build();
-
-        assertEquals(TEXT, request.getText());
-        assertEquals(LOCALES, request.getDefaultLocales());
-        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
-        assertEquals(1, request.getEntityConfig().getHints().size());
-        assertEquals(
-                TextClassifier.HINT_TEXT_IS_EDITABLE,
-                request.getEntityConfig().getHints().iterator().next());
-        assertEquals(referenceTime, request.getReferenceTime());
-    }
-
-    @Test
-    public void testTextLanguage() {
-        final TextLanguage language = new TextLanguage.Builder()
-                .setId(ID)
-                .putLocale(ULocale.ENGLISH, 0.6f)
-                .putLocale(ULocale.CHINESE, 0.3f)
-                .putLocale(ULocale.JAPANESE, 0.1f)
-                .setExtras(BUNDLE)
-                .build();
-
-        assertEquals(ID, language.getId());
-        assertEquals(3, language.getLocaleHypothesisCount());
-        assertEquals(ULocale.ENGLISH, language.getLocale(0));
-        assertEquals(ULocale.CHINESE, language.getLocale(1));
-        assertEquals(ULocale.JAPANESE, language.getLocale(2));
-        assertEquals(0.6f, language.getConfidenceScore(ULocale.ENGLISH), EPSILON);
-        assertEquals(0.3f, language.getConfidenceScore(ULocale.CHINESE), EPSILON);
-        assertEquals(0.1f, language.getConfidenceScore(ULocale.JAPANESE), EPSILON);
-        assertEquals(BUNDLE_VALUE, language.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextLanguage_clippedScore() {
-        final TextLanguage language = new TextLanguage.Builder()
-                .putLocale(ULocale.ENGLISH, 2f)
-                .putLocale(ULocale.CHINESE, -2f)
-                .build();
-
-        assertEquals(1, language.getLocaleHypothesisCount());
-        assertEquals(ULocale.ENGLISH, language.getLocale(0));
-        assertEquals(1f, language.getConfidenceScore(ULocale.ENGLISH), EPSILON);
-        assertNull(language.getId());
-    }
-
-    @Test
-    public void testTextLanguageRequest() {
-        final TextLanguage.Request request = new TextLanguage.Request.Builder(TEXT)
-                .setExtras(BUNDLE)
-                .build();
-
-        assertEquals(TEXT, request.getText());
-        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
-        assertNull(request.getCallingPackageName());
-    }
-
-    // TODO: Add more tests.
-
-    /** Helper to generate Icons for testing. */
-    private Icon generateTestIcon(int width, int height, int colorValue) {
-        final int numPixels = width * height;
-        final int[] colors = new int[numPixels];
-        for (int i = 0; i < numPixels; ++i) {
-            colors[i] = colorValue;
-        }
-        final Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
-        return Icon.createWithBitmap(bitmap);
-    }
-}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLanguageTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLanguageTest.java
new file mode 100644
index 0000000..52aa318
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLanguageTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.icu.util.ULocale;
+import android.os.Bundle;
+import android.view.textclassifier.TextLanguage;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextLanguageTest {
+    private static final float EPSILON = 0.000001f;
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+    private static final String ID = "id123";
+    private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
+
+    @Test
+    public void testTextLanguage() {
+        final TextLanguage language = new TextLanguage.Builder()
+                .setId(ID)
+                .putLocale(ULocale.ENGLISH, 0.6f)
+                .putLocale(ULocale.CHINESE, 0.3f)
+                .putLocale(ULocale.JAPANESE, 0.1f)
+                .setExtras(BUNDLE)
+                .build();
+
+        assertEquals(ID, language.getId());
+        assertEquals(3, language.getLocaleHypothesisCount());
+        assertEquals(ULocale.ENGLISH, language.getLocale(0));
+        assertEquals(ULocale.CHINESE, language.getLocale(1));
+        assertEquals(ULocale.JAPANESE, language.getLocale(2));
+        assertEquals(0.6f, language.getConfidenceScore(ULocale.ENGLISH), EPSILON);
+        assertEquals(0.3f, language.getConfidenceScore(ULocale.CHINESE), EPSILON);
+        assertEquals(0.1f, language.getConfidenceScore(ULocale.JAPANESE), EPSILON);
+        assertEquals(BUNDLE_VALUE, language.getExtras().getString(BUNDLE_KEY));
+    }
+
+    @Test
+    public void testTextLanguage_clippedScore() {
+        final TextLanguage language = new TextLanguage.Builder()
+                .putLocale(ULocale.ENGLISH, 2f)
+                .putLocale(ULocale.CHINESE, -2f)
+                .build();
+
+        assertEquals(1, language.getLocaleHypothesisCount());
+        assertEquals(ULocale.ENGLISH, language.getLocale(0));
+        assertEquals(1f, language.getConfidenceScore(ULocale.ENGLISH), EPSILON);
+        assertNull(language.getId());
+    }
+
+    @Test
+    public void testTextLanguageRequest() {
+        final TextLanguage.Request request = new TextLanguage.Request.Builder(TEXT)
+                .setExtras(BUNDLE)
+                .build();
+
+        assertEquals(TEXT, request.getText());
+        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
+        assertNull(request.getCallingPackageName());
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java
new file mode 100644
index 0000000..1414ed7
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.URLSpan;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextLinksTest {
+
+    private static final float EPSILON = 0.000001f;
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+
+    private static final double ACCEPTED_DELTA = 0.0000001;
+    private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
+    private static final int START = 5;
+    private static final int END = 20;
+    private static final int ANOTHER_START = 22;
+    private static final int ANOTHER_END = 24;
+    private static final LocaleList LOCALES = LocaleList.forLanguageTags("fr,en,de,es");
+
+    @Test
+    public void testTextLinks_defaultValues() {
+        final TextLinks textLinks = new TextLinks.Builder(TEXT).build();
+
+        assertEquals(TEXT, textLinks.getText());
+        assertTrue(textLinks.getExtras().isEmpty());
+        assertTrue(textLinks.getLinks().isEmpty());
+    }
+
+    @Test
+    public void testTextLinks_full() {
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .setExtras(BUNDLE)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_PHONE, 1.0f),
+                        BUNDLE)
+                .build();
+
+        assertEquals(TEXT, textLinks.getText());
+        assertEquals(BUNDLE_VALUE, textLinks.getExtras().getString(BUNDLE_KEY));
+        assertEquals(2, textLinks.getLinks().size());
+
+        final List<TextLinks.TextLink> resultList = new ArrayList<>(textLinks.getLinks());
+        final TextLinks.TextLink textLinkNoExtra = resultList.get(0);
+        assertEquals(TextClassifier.TYPE_ADDRESS, textLinkNoExtra.getEntity(0));
+        assertEquals(1.0f, textLinkNoExtra.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                EPSILON);
+        assertEquals(Bundle.EMPTY, textLinkNoExtra.getExtras());
+
+        final TextLinks.TextLink textLinkHasExtra = resultList.get(1);
+        assertEquals(TextClassifier.TYPE_PHONE, textLinkHasExtra.getEntity(0));
+        assertEquals(1.0f, textLinkHasExtra.getConfidenceScore(TextClassifier.TYPE_PHONE),
+                EPSILON);
+        assertEquals(BUNDLE_VALUE, textLinkHasExtra.getExtras().getString(BUNDLE_KEY));
+    }
+
+    @Test
+    public void testTextLinks_clearTextLinks() {
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .setExtras(BUNDLE)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .clearTextLinks()
+                .build();
+        assertEquals(0, textLinks.getLinks().size());
+    }
+
+    @Test
+    public void testTextLinks_apply() {
+        final SpannableString spannableString = SpannableString.valueOf(TEXT);
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .addLink(ANOTHER_START, ANOTHER_END,
+                        ImmutableMap.of(TextClassifier.TYPE_PHONE, 1.0f,
+                                TextClassifier.TYPE_ADDRESS, 0.5f))
+                .build();
+
+        final int status = textLinks.apply(
+                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, null);
+        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
+                spannableString.length() - 1,
+                TextLinks.TextLinkSpan.class);
+
+        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
+        assertEquals(2, textLinkSpans.length);
+
+        final TextLinks.TextLink textLink = textLinkSpans[0].getTextLink();
+        final TextLinks.TextLink anotherTextLink = textLinkSpans[1].getTextLink();
+
+        assertEquals(START, textLink.getStart());
+        assertEquals(END, textLink.getEnd());
+        assertEquals(1, textLink.getEntityCount());
+        assertEquals(TextClassifier.TYPE_ADDRESS, textLink.getEntity(0));
+        assertEquals(1.0f, textLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+        assertEquals(ANOTHER_START, anotherTextLink.getStart());
+        assertEquals(ANOTHER_END, anotherTextLink.getEnd());
+        assertEquals(2, anotherTextLink.getEntityCount());
+        assertEquals(TextClassifier.TYPE_PHONE, anotherTextLink.getEntity(0));
+        assertEquals(1.0f, anotherTextLink.getConfidenceScore(TextClassifier.TYPE_PHONE),
+                ACCEPTED_DELTA);
+        assertEquals(TextClassifier.TYPE_ADDRESS, anotherTextLink.getEntity(1));
+        assertEquals(0.5f, anotherTextLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+    }
+
+    @Test
+    public void testTextLinks_applyStrategyReplace() {
+        final SpannableString spannableString = SpannableString.valueOf(TEXT);
+        final URLSpan urlSpan = new URLSpan("http://www.google.com");
+        spannableString.setSpan(urlSpan, START, END, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .build();
+
+        final int status = textLinks.apply(
+                spannableString, TextLinks.APPLY_STRATEGY_REPLACE, null);
+        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
+                spannableString.length() - 1,
+                TextLinks.TextLinkSpan.class);
+        final URLSpan[] urlSpans = spannableString.getSpans(0, spannableString.length() - 1,
+                URLSpan.class);
+
+        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
+        assertEquals(1, textLinkSpans.length);
+        assertEquals(0, urlSpans.length);
+    }
+
+    @Test
+    public void testTextLinks_applyStrategyIgnore() {
+        final SpannableString spannableString = SpannableString.valueOf(TEXT);
+        final URLSpan urlSpan = new URLSpan("http://www.google.com");
+        spannableString.setSpan(urlSpan, START, END, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .build();
+
+        final int status = textLinks.apply(
+                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, null);
+        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
+                spannableString.length() - 1,
+                TextLinks.TextLinkSpan.class);
+        final URLSpan[] urlSpans = spannableString.getSpans(0, spannableString.length() - 1,
+                URLSpan.class);
+
+        assertEquals(TextLinks.STATUS_NO_LINKS_APPLIED, status);
+        assertEquals(0, textLinkSpans.length);
+        assertEquals(1, urlSpans.length);
+    }
+
+    @Test
+    public void testTextLinks_applyWithCustomSpanFactory() {
+        final class CustomTextLinkSpan extends TextLinks.TextLinkSpan {
+            private CustomTextLinkSpan(TextLinks.TextLink textLink) {
+                super(textLink);
+            }
+        }
+        final SpannableString spannableString = SpannableString.valueOf(TEXT);
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .build();
+
+        final int status = textLinks.apply(
+                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, CustomTextLinkSpan::new);
+        final CustomTextLinkSpan[] customTextLinkSpans = spannableString.getSpans(0,
+                spannableString.length() - 1,
+                CustomTextLinkSpan.class);
+
+        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
+        assertEquals(1, customTextLinkSpans.length);
+
+        final TextLinks.TextLink textLink = customTextLinkSpans[0].getTextLink();
+
+        assertEquals(START, textLink.getStart());
+        assertEquals(END, textLink.getEnd());
+        assertEquals(1, textLink.getEntityCount());
+        assertEquals(TextClassifier.TYPE_ADDRESS, textLink.getEntity(0));
+        assertEquals(1.0f, textLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+    }
+
+    @Test
+    public void testTextLinksRequest_defaultValues() {
+        final TextLinks.Request request = new TextLinks.Request.Builder(TEXT).build();
+
+        assertEquals(TEXT, request.getText());
+        assertNull(request.getDefaultLocales());
+        assertTrue(request.getExtras().isEmpty());
+        assertNull(request.getEntityConfig());
+    }
+
+    @Test
+    public void testTextLinksRequest_full() {
+        final ZonedDateTime referenceTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(1000L),
+                ZoneId.of("UTC"));
+        final TextLinks.Request request = new TextLinks.Request.Builder(TEXT)
+                .setDefaultLocales(LOCALES)
+                .setExtras(BUNDLE)
+                .setEntityConfig(TextClassifier.EntityConfig.createWithHints(
+                        Collections.singletonList(TextClassifier.HINT_TEXT_IS_EDITABLE)))
+                .setReferenceTime(referenceTime)
+                .build();
+
+        assertEquals(TEXT, request.getText());
+        assertEquals(LOCALES, request.getDefaultLocales());
+        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
+        assertEquals(1, request.getEntityConfig().getHints().size());
+        assertEquals(
+                TextClassifier.HINT_TEXT_IS_EDITABLE,
+                request.getEntityConfig().getHints().iterator().next());
+        assertEquals(referenceTime, request.getReferenceTime());
+    }
+
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextSelectionTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextSelectionTest.java
new file mode 100644
index 0000000..121243a
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextSelectionTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextSelection;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextSelectionTest {
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+
+    private static final double ACCEPTED_DELTA = 0.0000001;
+    private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
+    private static final int START = 5;
+    private static final int END = 20;
+    private static final String ID = "id123";
+    private static final LocaleList LOCALES = LocaleList.forLanguageTags("fr,en,de,es");
+
+    @Test
+    public void testTextSelection() {
+        final float addressScore = 0.1f;
+        final float emailScore = 0.9f;
+
+        final TextSelection selection = new TextSelection.Builder(START, END)
+                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
+                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
+                .setId(ID)
+                .setExtras(BUNDLE)
+                .build();
+
+        assertEquals(START, selection.getSelectionStartIndex());
+        assertEquals(END, selection.getSelectionEndIndex());
+        assertEquals(2, selection.getEntityCount());
+        assertEquals(TextClassifier.TYPE_EMAIL, selection.getEntity(0));
+        assertEquals(TextClassifier.TYPE_ADDRESS, selection.getEntity(1));
+        assertEquals(addressScore, selection.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+        assertEquals(emailScore, selection.getConfidenceScore(TextClassifier.TYPE_EMAIL),
+                ACCEPTED_DELTA);
+        assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+        assertEquals(ID, selection.getId());
+        assertEquals(BUNDLE_VALUE, selection.getExtras().getString(BUNDLE_KEY));
+    }
+
+    @Test
+    public void testTextSelection_differentParams() {
+        final int start = 0;
+        final int end = 1;
+        final float confidenceScore = 0.5f;
+        final String id = "2hukwu3m3k44f1gb0";
+
+        final TextSelection selection = new TextSelection.Builder(start, end)
+                .setEntityType(TextClassifier.TYPE_URL, confidenceScore)
+                .setId(id)
+                .build();
+
+        assertEquals(start, selection.getSelectionStartIndex());
+        assertEquals(end, selection.getSelectionEndIndex());
+        assertEquals(1, selection.getEntityCount());
+        assertEquals(TextClassifier.TYPE_URL, selection.getEntity(0));
+        assertEquals(confidenceScore, selection.getConfidenceScore(TextClassifier.TYPE_URL),
+                ACCEPTED_DELTA);
+        assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+        assertEquals(id, selection.getId());
+    }
+
+    @Test
+    public void testTextSelection_defaultValues() {
+        TextSelection selection = new TextSelection.Builder(START, END).build();
+        assertEquals(0, selection.getEntityCount());
+        assertNull(selection.getId());
+        assertTrue(selection.getExtras().isEmpty());
+    }
+
+    @Test
+    public void testTextSelection_prunedConfidenceScore() {
+        final float phoneScore = -0.1f;
+        final float prunedPhoneScore = 0f;
+        final float otherScore = 1.5f;
+        final float prunedOtherScore = 1.0f;
+
+        final TextSelection selection = new TextSelection.Builder(START, END)
+                .setEntityType(TextClassifier.TYPE_PHONE, phoneScore)
+                .setEntityType(TextClassifier.TYPE_OTHER, otherScore)
+                .build();
+
+        assertEquals(prunedPhoneScore, selection.getConfidenceScore(TextClassifier.TYPE_PHONE),
+                ACCEPTED_DELTA);
+        assertEquals(prunedOtherScore, selection.getConfidenceScore(TextClassifier.TYPE_OTHER),
+                ACCEPTED_DELTA);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTextSelection_invalidStartParams() {
+        new TextSelection.Builder(-1 /* start */, END)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTextSelection_invalidEndParams() {
+        new TextSelection.Builder(START, 0 /* end */)
+                .build();
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testTextSelection_entityIndexOutOfBounds() {
+        final TextSelection selection = new TextSelection.Builder(START, END).build();
+        final int outOfBoundsIndex = selection.getEntityCount();
+        selection.getEntity(outOfBoundsIndex);
+    }
+
+    @Test
+    public void testTextSelectionRequest() {
+        final TextSelection.Request request = new TextSelection.Request.Builder(TEXT, START, END)
+                .setDefaultLocales(LOCALES)
+                .setExtras(BUNDLE)
+                .build();
+        assertEquals(TEXT, request.getText().toString());
+        assertEquals(START, request.getStartIndex());
+        assertEquals(END, request.getEndIndex());
+        assertEquals(LOCALES, request.getDefaultLocales());
+        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
+    }
+
+    @Test
+    public void testTextSelectionRequest_nullValues() {
+        final TextSelection.Request request =
+                new TextSelection.Request.Builder(TEXT, START, END)
+                        .setDefaultLocales(null)
+                        .build();
+        assertNull(request.getDefaultLocales());
+    }
+
+    @Test
+    public void testTextSelectionRequest_defaultValues() {
+        final TextSelection.Request request =
+                new TextSelection.Request.Builder(TEXT, START, END).build();
+        assertNull(request.getDefaultLocales());
+        assertTrue(request.getExtras().isEmpty());
+    }
+}
diff --git a/tests/tests/time/Android.bp b/tests/tests/time/Android.bp
new file mode 100644
index 0000000..6dedbff
--- /dev/null
+++ b/tests/tests/time/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test {
+    name: "CtsTimeTestCases",
+    defaults: ["cts_defaults"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+    ],
+    srcs: ["src/**/*.java"],
+    sdk_version: "system_current",
+}
diff --git a/tests/tests/time/AndroidManifest.xml b/tests/tests/time/AndroidManifest.xml
new file mode 100644
index 0000000..a3bd06d
--- /dev/null
+++ b/tests/tests/time/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="android.time.cts">
+
+    <!-- The permissions below would be needed if tests were not using "adopt shell permissions" to
+         obtain the necessary MANAGE_TIME_AND_ZONE_DETECTION privileged permission. -->
+    <!-- uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" /-->
+    <!-- Required for LocationManager.setLocationEnabledForUser() -->
+    <!-- uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/-->
+    <eat-comment />
+
+    <application>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.time.cts"
+        android:label="CTS tests for android.time">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/time/AndroidTest.xml b/tests/tests/time/AndroidTest.xml
new file mode 100644
index 0000000..1036c1a
--- /dev/null
+++ b/tests/tests/time/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for Toast test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="not-shardable" value="true" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsTimeTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.time.cts" />
+    </test>
+</configuration>
diff --git a/tests/tests/time/OWNERS b/tests/tests/time/OWNERS
new file mode 100644
index 0000000..ff89e51
--- /dev/null
+++ b/tests/tests/time/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 847766
+nfuller@google.com
+vichang@google.com
diff --git a/tests/tests/time/src/android/time/cts/TimeManagerTest.java b/tests/tests/time/src/android/time/cts/TimeManagerTest.java
new file mode 100644
index 0000000..bdca042
--- /dev/null
+++ b/tests/tests/time/src/android/time/cts/TimeManagerTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.time.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.time.TimeManager;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
+import android.content.Context;
+import android.location.LocationManager;
+import android.os.UserHandle;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/** Tests for {@link TimeManager} and associated classes. */
+public class TimeManagerTest {
+
+    /**
+     * This rule adopts the Shell process permissions, needed because MANAGE_TIME_AND_ZONE_DETECTION
+     * is a privileged permission.
+     */
+    @Rule
+    public final AdoptShellPermissionsRule shellPermRule = new AdoptShellPermissionsRule();
+
+    /**
+     * Registers a {@link android.app.time.TimeManager.TimeZoneDetectorListener}, makes changes
+     * to the configuration and checks that the listener is called.
+     */
+    @Test
+    public void testManageConfiguration() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+
+        int expectedListenerTriggerCount = 0;
+        AtomicInteger listenerTriggerCount = new AtomicInteger(0);
+        TimeManager.TimeZoneDetectorListener listener = listenerTriggerCount::incrementAndGet;
+
+        TimeManager timeManager = context.getSystemService(TimeManager.class);
+        assertNotNull(timeManager);
+
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        try {
+            timeManager.addTimeZoneDetectorListener(executor, listener);
+            waitForListenerCallbackCount(expectedListenerTriggerCount, listenerTriggerCount);
+
+            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                    timeManager.getTimeZoneCapabilitiesAndConfig();
+            waitForListenerCallbackCount(expectedListenerTriggerCount, listenerTriggerCount);
+
+            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneConfiguration originalConfig = capabilitiesAndConfig.getConfiguration();
+
+            // Toggle the auto-detection enabled if capabilities allow or try (but expect to fail)
+            // if not.
+            {
+                boolean newAutoDetectionEnabledValue = !originalConfig.isAutoDetectionEnabled();
+                TimeZoneConfiguration configUpdate = new TimeZoneConfiguration.Builder()
+                        .setAutoDetectionEnabled(newAutoDetectionEnabledValue)
+                        .build();
+                if (capabilities.getConfigureAutoDetectionEnabledCapability()
+                        >= TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE) {
+                    assertTrue(timeManager.updateTimeZoneConfiguration(configUpdate));
+                    expectedListenerTriggerCount++;
+                } else {
+                    assertFalse(timeManager.updateTimeZoneConfiguration(configUpdate));
+                }
+            }
+            waitForListenerCallbackCount(expectedListenerTriggerCount, listenerTriggerCount);
+
+            // Toggle the geo-detection enabled if capabilities allow or try (but expect to fail)
+            // if not.
+            {
+                boolean newGeoDetectionEnabledValue = !originalConfig.isGeoDetectionEnabled();
+                TimeZoneConfiguration configUpdate = new TimeZoneConfiguration.Builder()
+                        .setGeoDetectionEnabled(newGeoDetectionEnabledValue)
+                        .build();
+                if (capabilities.getConfigureGeoDetectionEnabledCapability()
+                        >= TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE) {
+                    assertTrue(timeManager.updateTimeZoneConfiguration(configUpdate));
+                    expectedListenerTriggerCount++;
+                } else {
+                    assertFalse(timeManager.updateTimeZoneConfiguration(configUpdate));
+                }
+            }
+            waitForListenerCallbackCount(expectedListenerTriggerCount, listenerTriggerCount);
+
+            // Reset the config to what it was when the test started, if needed.
+            if (expectedListenerTriggerCount > 0) {
+                assertTrue(timeManager.updateTimeZoneConfiguration(originalConfig));
+                expectedListenerTriggerCount++;
+            }
+            waitForListenerCallbackCount(expectedListenerTriggerCount, listenerTriggerCount);
+        } finally {
+            // Remove the listener. Required otherwise the fuzzy equality rules of lambdas causes
+            // problems for later tests.
+            timeManager.removeTimeZoneDetectorListener(listener);
+
+            executor.shutdown();
+        }
+    }
+
+    /**
+     * Registers a {@link android.app.time.TimeManager.TimeZoneDetectorListener}, makes changes
+     * to the "location enabled" setting and checks that the listener is called.
+     */
+    @Test
+    public void testLocationManagerAffectsCapabilities() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+
+        AtomicInteger listenerTriggerCount = new AtomicInteger(0);
+        TimeManager.TimeZoneDetectorListener listener = listenerTriggerCount::incrementAndGet;
+
+        TimeManager timeManager = context.getSystemService(TimeManager.class);
+        assertNotNull(timeManager);
+
+        LocationManager locationManager = context.getSystemService(LocationManager.class);
+        assertNotNull(locationManager);
+
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        try {
+            timeManager.addTimeZoneDetectorListener(executor, listener);
+            waitForListenerCallbackCount(0, listenerTriggerCount);
+
+            UserHandle userHandle = android.os.Process.myUserHandle();
+            boolean locationEnabled = locationManager.isLocationEnabledForUser(userHandle);
+
+            locationManager.setLocationEnabledForUser(!locationEnabled, userHandle);
+            waitForListenerCallbackCount(1, listenerTriggerCount);
+
+            locationManager.setLocationEnabledForUser(locationEnabled, userHandle);
+            waitForListenerCallbackCount(2, listenerTriggerCount);
+        } finally {
+            // Remove the listener. Required otherwise the fuzzy equality rules of lambdas causes
+            // problems for later tests.
+            timeManager.removeTimeZoneDetectorListener(listener);
+
+            executor.shutdown();
+        }
+    }
+
+    private static void waitForListenerCallbackCount(
+            int expectedValue, AtomicInteger actualValue) throws Exception {
+        // Busy waits up to 30 seconds for the count to reach the expected value.
+        final long busyWaitMillis = 30000;
+        long targetTimeMillis = System.currentTimeMillis() + busyWaitMillis;
+        while (expectedValue != actualValue.get()
+                && System.currentTimeMillis() < targetTimeMillis) {
+            Thread.sleep(250);
+        }
+        assertEquals(expectedValue, actualValue.get());
+    }
+}
diff --git a/tests/tests/tv/AndroidManifest.xml b/tests/tests/tv/AndroidManifest.xml
index a3429bb..b8b4ca7 100644
--- a/tests/tests/tv/AndroidManifest.xml
+++ b/tests/tests/tv/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -17,114 +16,125 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.tv.cts">
+     package="android.tv.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
 
-    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
 
     <queries>
-        <package android:name="com.android.providers.tv" />
+        <package android:name="com.android.providers.tv"/>
     </queries>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="android.media.tv.cts.TvInputSetupActivityStub">
+        <activity android:name="android.media.tv.cts.TvInputSetupActivityStub"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.media.tv.cts.TvInputSettingsActivityStub">
+        <activity android:name="android.media.tv.cts.TvInputSettingsActivityStub"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
         <service android:name="android.media.tv.cts.StubTunerTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT"
-                 android:label="TV input stub"
-                 android:icon="@drawable/robot"
-                 android:process=":tunerTvInputStub">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:label="TV input stub"
+             android:icon="@drawable/robot"
+             android:process=":tunerTvInputStub"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
         <service android:name="android.media.tv.cts.NoMetadataTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
         </service>
 
-        <service android:name="android.media.tv.cts.NoPermissionTvInputService">
+        <service android:name="android.media.tv.cts.NoPermissionTvInputService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
         <service android:name="android.media.tv.cts.TvInputManagerTest$StubTvInputService2"
-                 android:permission="android.permission.BIND_TV_INPUT">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
         <service android:name="android.media.tv.cts.TvInputServiceTest$CountingTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
         <service android:name="android.media.tv.cts.HardwareSessionTest$HardwareProxyTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
         <service android:name="android.media.tv.cts.FaultyTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT"
-                 android:process=":faultyTvInputService">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:process=":faultyTvInputService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
-        <activity android:name="android.media.tv.cts.TvViewStubActivity">
+        <activity android:name="android.media.tv.cts.TvViewStubActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.tv.settings.cts.SettingsLeanbackStubActivity">
+        <activity android:name="android.tv.settings.cts.SettingsLeanbackStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="android.tv.cts"
-            android:label="Tests for the TV APIs.">
+         android:targetPackage="android.tv.cts"
+         android:label="Tests for the TV APIs.">
         <meta-data android:name="listener"
-                android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/tests/tests/uiautomation/AndroidManifest.xml b/tests/tests/uiautomation/AndroidManifest.xml
index fdc7457..34d8def 100644
--- a/tests/tests/uiautomation/AndroidManifest.xml
+++ b/tests/tests/uiautomation/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -17,50 +16,47 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.app.uiautomation.cts"
-        android:targetSandboxVersion="2">
+     package="android.app.uiautomation.cts"
+     android:targetSandboxVersion="2">
 
-  <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-  <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-  <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-  <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
+  <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+  <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+  <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+  <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>
 
   <application android:theme="@android:style/Theme.Holo.NoActionBar"
-          android:requestLegacyExternalStorage="true">
+       android:requestLegacyExternalStorage="true">
 
       <uses-library android:name="android.test.runner"/>
 
-      <activity
-          android:name="android.app.uiautomation.cts.UiAutomationTestFirstActivity"
-          android:exported="true">
+      <activity android:name="android.app.uiautomation.cts.UiAutomationTestFirstActivity"
+           android:exported="true">
       </activity>
 
-      <activity
-          android:name="android.app.uiautomation.cts.UiAutomationTestSecondActivity"
-          android:exported="true">
+      <activity android:name="android.app.uiautomation.cts.UiAutomationTestSecondActivity"
+           android:exported="true">
       </activity>
 
-      <service
-              android:name="android.app.uiautomation.cts.UiAutomationTestA11yService"
-              android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
+      <service android:name="android.app.uiautomation.cts.UiAutomationTestA11yService"
+           android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.accessibilityservice.AccessibilityService" />
+              <action android:name="android.accessibilityservice.AccessibilityService"/>
 
-              <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+              <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
           </intent-filter>
 
-          <meta-data
-                  android:name="android.accessibilityservice"
-                  android:resource="@xml/ui_automation_test_a11y_service" />
+          <meta-data android:name="android.accessibilityservice"
+               android:resource="@xml/ui_automation_test_a11y_service"/>
       </service>
 
   </application>
 
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                   android:targetPackage="android.app.uiautomation.cts">
+       android:targetPackage="android.app.uiautomation.cts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/uiautomation/OWNERS b/tests/tests/uiautomation/OWNERS
index a98c458..bf9a18d 100644
--- a/tests/tests/uiautomation/OWNERS
+++ b/tests/tests/uiautomation/OWNERS
@@ -1,3 +1,5 @@
 # Bug component: 44215
 pweaver@google.com
 rhedjao@google.com
+qasid@google.com
+ryanlwlin@google.com
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
index 9135e56..e9c050a 100755
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
@@ -24,6 +24,7 @@
 
 import android.Manifest;
 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -39,10 +40,12 @@
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.view.FrameStats;
+import android.view.KeyEvent;
 import android.view.WindowAnimationFrameStats;
 import android.view.WindowContentFrameStats;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.widget.ListView;
 
@@ -91,24 +94,17 @@
         final PackageManager packageManager = context.getPackageManager();
 
         // Try to access APIs guarded by a platform defined signature permissions
-        try {
-            activityManager.getPackageImportance("foo.bar.baz");
-            fail("Should not be able to access APIs protected by a permission apps cannot get");
-        } catch (SecurityException e) {
-            /* expected */
-        }
-        try {
-            packageManager.grantRuntimePermission(context.getPackageName(),
-                    Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle());
-            fail("Should not be able to access APIs protected by a permission apps cannot get");
-        } catch (SecurityException e) {
-            /* expected */
-        }
+        assertThrows(SecurityException.class,
+                () -> activityManager.getPackageImportance("foo.bar.baz"),
+                "Should not be able to access APIs protected by a permission apps cannot get");
+        assertThrows(SecurityException.class,
+                () -> packageManager.grantRuntimePermission(context.getPackageName(),
+                        Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle()),
+                "Should not be able to access APIs protected by a permission apps cannot get");
 
         // Access APIs guarded by a platform defined signature permissions
         try {
             getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
-
             // Access APIs guarded by a platform defined signature permission
             activityManager.getPackageImportance("foo.bar.baz");
 
@@ -128,19 +124,13 @@
 
 
         // Try to access APIs guarded by a platform defined signature permissions
-        try {
-            activityManager.getPackageImportance("foo.bar.baz");
-            fail("Should not be able to access APIs protected by a permission apps cannot get");
-        } catch (SecurityException e) {
-            /* expected */
-        }
-        try {
-            packageManager.revokeRuntimePermission(context.getPackageName(),
-                    Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle());
-            fail("Should not be able to access APIs protected by a permission apps cannot get");
-        } catch (SecurityException e) {
-            /* expected */
-        }
+        assertThrows(SecurityException.class,
+                () -> activityManager.getPackageImportance("foo.bar.baz"),
+                "Should not be able to access APIs protected by a permission apps cannot get");
+        assertThrows(SecurityException.class,
+                () -> packageManager.revokeRuntimePermission(context.getPackageName(),
+                        Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle()),
+                "Should not be able to access APIs protected by a permission apps cannot get");
     }
 
     @AppModeFull
@@ -402,11 +392,8 @@
     public void testUsingUiAutomationAfterDestroy_shouldThrowException() {
         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
         uiAutomation.destroy();
-        try {
-            uiAutomation.getServiceInfo();
-            fail("Expected exception when using destroyed UiAutomation");
-        } catch (RuntimeException e) {
-        }
+        assertThrows(RuntimeException.class, () -> uiAutomation.getServiceInfo(),
+                "Expected exception when using destroyed UiAutomation");
     }
 
     @AppModeFull
@@ -443,20 +430,14 @@
 
     @AppModeFull
     @Test
-    public void testServiceSupressingA11yServices_a11yServiceStartsWhenDestroyed()
-            throws Exception {
+    public void testServiceWithDontUseAccessibilityFlag_shutsDownA11yService() throws Exception {
         turnAccessibilityOff();
         try {
-            UiAutomation uiAutomation = getInstrumentation()
-                    .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
             enableAccessibilityService();
-            uiAutomation.destroy();
-            UiAutomation suppressingUiAutomation = getInstrumentation().getUiAutomation();
-            // We verify above that the connection is broken here. Make sure we see a new one
-            // after we destroy it
+            assertTrue(UiAutomationTestA11yService.sConnectedInstance.isConnected());
+            getInstrumentation().getUiAutomation(
+                    UiAutomation.FLAG_DONT_USE_ACCESSIBILITY); // Should suppress
             waitForAccessibilityServiceToUnbind();
-            suppressingUiAutomation.destroy();
-            waitForAccessibilityServiceToStart();
         } finally {
             turnAccessibilityOff();
         }
@@ -464,7 +445,42 @@
 
     @AppModeFull
     @Test
-    public void testServiceSupressingA11yServices_a11yServiceStartsWhenFlagsChange()
+    public void testServiceSuppressingA11yServices_a11yServiceStartsWhenDestroyed()
+            throws Exception {
+        turnAccessibilityOff();
+        try {
+            UiAutomation uiAutomation = getInstrumentation()
+                    .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+            enableAccessibilityService();
+            uiAutomation.destroy();
+
+            assertA11yServiceSuppressedAndRestartsAfterUiAutomationDestroyed(0);
+        } finally {
+            turnAccessibilityOff();
+        }
+    }
+
+    @AppModeFull
+    @Test
+    public void testServiceSuppressingA11yServices_a11yServiceStartsWhenDestroyedAndFlagChanged()
+            throws Exception {
+        turnAccessibilityOff();
+        try {
+            UiAutomation uiAutomation = getInstrumentation()
+                    .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+            enableAccessibilityService();
+            uiAutomation.destroy();
+
+            assertA11yServiceSuppressedAndRestartsAfterUiAutomationDestroyed(
+                    UiAutomation.FLAG_DONT_USE_ACCESSIBILITY);
+        } finally {
+            turnAccessibilityOff();
+        }
+    }
+
+    @AppModeFull
+    @Test
+    public void testServiceSuppressingA11yServices_a11yServiceStartsWhenFlagsChange()
             throws Exception {
         turnAccessibilityOff();
         try {
@@ -483,6 +499,61 @@
         }
     }
 
+    @AppModeFull
+    @Test
+    public void testCallingDisabledAccessibilityAPIsWithDontUseAccessibilityFlag_shouldThrowException()
+            throws Exception {
+        final UiAutomation uiAutomation = getInstrumentation()
+                .getUiAutomation(UiAutomation.FLAG_DONT_USE_ACCESSIBILITY);
+        final String failMsg =
+                "Should not be able to access Accessibility APIs disabled by UiAutomation flag, "
+                        + "FLAG_DONT_USE_ACCESSIBILITY";
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK),
+                failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.findFocus(AccessibilityNodeInfo.FOCUS_INPUT), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.getServiceInfo(), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.setServiceInfo(new AccessibilityServiceInfo()), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.findFocus(AccessibilityNodeInfo.FOCUS_INPUT), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.getWindows(), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.getWindowsOnAllDisplays(), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.clearWindowContentFrameStats(-1), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.getWindowContentFrameStats(-1), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.getRootInActiveWindow(), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.setOnAccessibilityEventListener(null), failMsg);
+    }
+
+    @AppModeFull
+    @Test
+    public void testCallingPublicAPIsWithDontUseAccessibilityFlag_shouldNotThrowException()
+            throws Exception {
+        final UiAutomation uiAutomation = getInstrumentation()
+                .getUiAutomation(UiAutomation.FLAG_DONT_USE_ACCESSIBILITY);
+        final KeyEvent event = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_BACK, 0);
+        uiAutomation.injectInputEvent(event, true);
+        uiAutomation.syncInputTransactions();
+        uiAutomation.setRotation(UiAutomation.ROTATION_FREEZE_0);
+        uiAutomation.takeScreenshot();
+        uiAutomation.clearWindowAnimationFrameStats();
+        uiAutomation.getWindowAnimationFrameStats();
+        try {
+            uiAutomation.adoptShellPermissionIdentity(Manifest.permission.BATTERY_STATS);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
     private void scrollListView(UiAutomation uiAutomation, final ListView listView,
             final int position) throws TimeoutException {
         getInstrumentation().runOnMainSync(new Runnable() {
@@ -541,7 +612,7 @@
     private void waitForAccessibilityServiceToStart() {
         long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
-            synchronized(UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind) {
+            synchronized (UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind) {
                 if (UiAutomationTestA11yService.sConnectedInstance != null) {
                     return;
                 }
@@ -559,7 +630,7 @@
     private void waitForAccessibilityServiceToUnbind() {
         long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
-            synchronized(UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind) {
+            synchronized (UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind) {
                 if (UiAutomationTestA11yService.sConnectedInstance == null) {
                     return;
                 }
@@ -652,6 +723,27 @@
         }
     }
 
+    // An actual version of assertThrows() was added in JUnit5
+    private static <T extends Throwable> void assertThrows(Class<T> clazz, Runnable r,
+            String message) {
+        try {
+            r.run();
+        } catch (Exception expected) {
+            assertTrue(clazz.isAssignableFrom(expected.getClass()));
+            return;
+        }
+        fail(message);
+    }
+
+    private void assertA11yServiceSuppressedAndRestartsAfterUiAutomationDestroyed(int flag) {
+        UiAutomation suppressingUiAutomation = getInstrumentation().getUiAutomation(flag);
+        // We verify above that the connection is broken here. Make sure we see a new one
+        // after we destroy it
+        waitForAccessibilityServiceToUnbind();
+        suppressingUiAutomation.destroy();
+        waitForAccessibilityServiceToStart();
+    }
+
     private int findAppWindowId(List<AccessibilityWindowInfo> windows) {
         final int windowCount = windows.size();
         for (int i = 0; i < windowCount; i++) {
diff --git a/tests/tests/uirendering/res/drawable-nodpi/extrabold1.png b/tests/tests/uirendering/res/drawable-nodpi/extrabold1.png
new file mode 100644
index 0000000..c247451
--- /dev/null
+++ b/tests/tests/uirendering/res/drawable-nodpi/extrabold1.png
Binary files differ
diff --git a/tests/tests/uirendering/res/drawable-nodpi/extrabolditalic1.png b/tests/tests/uirendering/res/drawable-nodpi/extrabolditalic1.png
new file mode 100644
index 0000000..5900db2
--- /dev/null
+++ b/tests/tests/uirendering/res/drawable-nodpi/extrabolditalic1.png
Binary files differ
diff --git a/tests/tests/uirendering/res/drawable-nodpi/lightitalic1.png b/tests/tests/uirendering/res/drawable-nodpi/lightitalic1.png
index ee3a210..f623854 100644
--- a/tests/tests/uirendering/res/drawable-nodpi/lightitalic1.png
+++ b/tests/tests/uirendering/res/drawable-nodpi/lightitalic1.png
Binary files differ
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/FontRenderingTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/FontRenderingTests.java
index 3b48542..16b9315 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/FontRenderingTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/FontRenderingTests.java
@@ -105,10 +105,9 @@
 
     @Test
     public void testMediumBoldFont() {
-        // bold attribute on medium base font = black
         fontTestBody("sans-serif-medium",
                 Typeface.BOLD,
-                R.drawable.black1);
+                R.drawable.extrabold1);
     }
 
     @Test
@@ -122,7 +121,7 @@
     public void testMediumBoldItalicFont() {
         fontTestBody("sans-serif-medium",
                 Typeface.BOLD | Typeface.ITALIC,
-                R.drawable.blackitalic1);
+                R.drawable.extrabolditalic1);
     }
 
     @Test
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareRendererTests.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareRendererTests.kt
index 0f82370..d4c2549 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareRendererTests.kt
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareRendererTests.kt
@@ -483,4 +483,10 @@
             reader.close()
         }
     }
+
+    @Test
+    fun testSetNullSurface() {
+        HardwareRenderer().setSurface(null)
+        // yay we didn't crash, test over
+    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
index 1c3f038..c38ff1c 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
@@ -49,6 +49,22 @@
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class PathClippingTests extends ActivityTestBase {
+    private static final String BLUE_RED_HTML =
+            "<html><head>"
+            + "    <style type=\"text/css\">"
+            + "        .container {"
+            + "            display: grid;"
+            + "            grid-template-columns: 50% 50%;"
+            + "            grid-template-rows: 100%;"
+            + "            margin: 0;"
+            + "        }"
+            + "    </style>"
+            + "</head><body class=\"container\">"
+            + "    <div style=\"background-color:blue\"></div>"
+            + "    <div style=\"background-color:red\"></div>"
+            + "</body></html>";
+
+
     // draw circle with hole in it, with stroked circle
     static final CanvasClient sTorusDrawCanvasClient = (canvas, width, height) -> {
         Paint paint = new Paint();
@@ -190,12 +206,12 @@
                 .runWithComparer(new MSSIMComparer(0.90));
     }
 
-    private ViewInitializer initBlueWebView(final CountDownLatch fence) {
+    private ViewInitializer initWebView(final CountDownLatch fence) {
         return view -> {
             WebView webview = (WebView)view.findViewById(R.id.webview);
             assertNotNull(webview);
             WebViewReadyHelper helper = new WebViewReadyHelper(webview, fence);
-            helper.loadData("<body style=\"background-color:blue\">");
+            helper.loadData(BLUE_RED_HTML);
         };
     }
 
@@ -208,18 +224,30 @@
         CountDownLatch hwFence = new CountDownLatch(1);
         CountDownLatch swFence = new CountDownLatch(1);
         createTest()
-                // golden client - draw a simple non-AA circle
+                // golden client - draw a non-AA circle. left half is blue and right half is red.
                 .addCanvasClient((canvas, width, height) -> {
                     Paint paint = new Paint();
                     paint.setAntiAlias(false);
+
+                    int halfWidth = width / 2;
+
+                    canvas.save();
                     paint.setColor(Color.BLUE);
+                    canvas.clipRect(0, 0, halfWidth, height);
                     canvas.drawOval(0, 0, width, height, paint);
+                    canvas.restore();
+
+                    canvas.save();
+                    paint.setColor(Color.RED);
+                    canvas.clipRect(halfWidth, 0, width, height);
+                    canvas.drawOval(0, 0, width, height, paint);
+                    canvas.restore();
                 }, false)
-                // verify against solid color webview, clipped to its parent oval
+                // verify against webview drawing blue and red rects, clipped to its parent oval
                 .addLayout(R.layout.circle_clipped_webview,
-                        initBlueWebView(hwFence), true, hwFence)
+                        initWebView(hwFence), true, hwFence)
                 .addLayout(R.layout.circle_clipped_webview,
-                        initBlueWebView(swFence), false, swFence)
+                        initWebView(swFence), false, swFence)
                 .runWithComparer(new MSSIMComparer(0.84f));
     }
 }
diff --git a/tests/tests/uirendering27/TEST_MAPPING b/tests/tests/uirendering27/TEST_MAPPING
index fa55ba8..318d99c 100644
--- a/tests/tests/uirendering27/TEST_MAPPING
+++ b/tests/tests/uirendering27/TEST_MAPPING
@@ -4,4 +4,4 @@
       "name": "CtsUiRenderingTestCases27"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/tests/tests/usb/TEST_MAPPING b/tests/tests/usb/TEST_MAPPING
new file mode 100644
index 0000000..cb22eb9
--- /dev/null
+++ b/tests/tests/usb/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsUsbManagerTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/util/TEST_MAPPING b/tests/tests/util/TEST_MAPPING
new file mode 100644
index 0000000..70f0e93
--- /dev/null
+++ b/tests/tests/util/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsUtilTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/util/src/android/util/cts/InstallUtilTest.java b/tests/tests/util/src/android/util/cts/InstallUtilTest.java
index c3bfe28..59e89aa 100644
--- a/tests/tests/util/src/android/util/cts/InstallUtilTest.java
+++ b/tests/tests/util/src/android/util/cts/InstallUtilTest.java
@@ -177,8 +177,9 @@
             assertThat(session).isNotNull();
 
             // Session can be committed directly, but a BroadcastReceiver must be provided.
-            session.commit(LocalIntentSender.getIntentSender());
-            InstallUtils.assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+            LocalIntentSender sender = new LocalIntentSender();
+            session.commit(sender.getIntentSender());
+            InstallUtils.assertStatusSuccess(sender.getResult());
 
             // Verify app has been installed
             assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
diff --git a/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java b/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java
index 96b54ee..93df3dc 100644
--- a/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java
+++ b/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java
@@ -37,7 +37,7 @@
 
     @Test
     public void testStoreSingleInt() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(0, KEYS_1[i], i);
         }
@@ -52,7 +52,7 @@
 
     @Test
     public void testStoreMultipleInt() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
 
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(0, KEYS_1[i], i);
@@ -76,7 +76,7 @@
 
     @Test
     public void testClear() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(0, KEYS_1[i], i);
         }
@@ -89,7 +89,7 @@
 
     @Test
     public void testContains() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(0, KEYS_1[i], i);
         }
@@ -105,7 +105,7 @@
 
     @Test
     public void testDelete() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(0, KEYS_1[i], i);
             sam.add(1, KEYS_1[i], i);
@@ -142,7 +142,7 @@
 
     @Test
     public void testGetOrDefault() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             if (i % 2 == 0) {
                 sam.add(0, KEYS_1[i], i);
@@ -157,7 +157,7 @@
 
     @Test
     public void testIntKeyIndexing() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(i * 2, KEYS_1[i], i * 2 + 1);
         }
@@ -169,7 +169,7 @@
 
     @Test
     public void testIntStringKeyIndexing() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(i * 2, KEYS_1[i], i * 2 + 1);
         }
@@ -182,7 +182,7 @@
 
     @Test
     public void testNumMaps() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < 10; i++) {
             assertEquals(i, sam.numMaps());
             sam.add(i, "blue", i);
diff --git a/tests/tests/view/Android.mk b/tests/tests/view/Android.mk
index dd0c5b2..ead7fa7 100644
--- a/tests/tests/view/Android.mk
+++ b/tests/tests/view/Android.mk
@@ -34,6 +34,7 @@
     compatibility-device-util-axt \
     ctsdeviceutillegacy-axt \
     ctstestrunner-axt \
+    cts-input-lib \
     mockito-target-minus-junit4 \
     platform-test-annotations \
     ub-uiautomator \
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 344375e..86527f7 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -16,383 +16,423 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.view.cts"
-    android:targetSandboxVersion="2">
+     package="android.view.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-feature android:name="android.hardware.camera" />
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-feature android:name="android.hardware.camera"/>
 
     <application android:label="Android TestCase"
-                android:icon="@drawable/size_48x48"
-                android:maxRecents="1"
-                android:multiArch="true"
-                android:supportsRtl="true">
-        <uses-library android:name="android.test.runner" />
+         android:icon="@drawable/size_48x48"
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:supportsRtl="true">
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.app.Activity"
-                  android:label="Empty Activity"
-                  android:theme="@style/ViewAttributeTestTheme">
+             android:label="Empty Activity"
+             android:theme="@style/ViewAttributeTestTheme"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ViewStubCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ViewStubCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ViewStubCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.UsingViewsCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="Using Views Test">
+             android:screenOrientation="locked"
+             android:label="Using Views Test"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.FocusHandlingCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="Focus Handling Test">
+             android:screenOrientation="locked"
+             android:label="Focus Handling Test"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name=".ViewGroupInvalidateChildCtsActivity"
-                  android:label="ViewGroupCtsActivity"
-                  android:screenOrientation="locked"
-                  android:hardwareAccelerated="false">
+             android:label="ViewGroupCtsActivity"
+             android:screenOrientation="locked"
+             android:hardwareAccelerated="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ViewTestCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ViewTestCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ViewTestCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ViewLayoutPositionTestCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ViewTestCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ViewTestCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.animation.cts.AnimationTestCtsActivity"
-                  android:label="AnimationTestCtsActivity"
-                  android:screenOrientation="locked"
-                  android:configChanges="orientation|screenSize">
+             android:label="AnimationTestCtsActivity"
+             android:screenOrientation="locked"
+             android:configChanges="orientation|screenSize"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.animation.cts.GridLayoutAnimCtsActivity"
-                  android:label="GridLayoutAnimCtsActivity"
-                  android:screenOrientation="locked"
-                  android:configChanges="orientation|screenSize">
+             android:label="GridLayoutAnimCtsActivity"
+             android:screenOrientation="locked"
+             android:configChanges="orientation|screenSize"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.animation.cts.LayoutAnimCtsActivity"
-                  android:label="LayoutAnimCtsActivity"
-                  android:screenOrientation="locked"
-                  android:configChanges="orientation|screenSize">
+             android:label="LayoutAnimCtsActivity"
+             android:screenOrientation="locked"
+             android:configChanges="orientation|screenSize"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.TextureViewCtsActivity"
-                  android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
-                  android:screenOrientation="locked"
-                  android:label="TextureViewCtsActivity">
+             android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+             android:screenOrientation="locked"
+             android:label="TextureViewCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.TextureViewCameraActivity"
-                  android:screenOrientation="locked">
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.TextureViewStressTestActivity"
-                  android:screenOrientation="locked">
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.TextureViewSnapshotTestActivity"
-                  android:screenOrientation="locked">
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.PixelCopyVideoSourceActivity"
-                  android:screenOrientation="locked"
-                  android:label="PixelCopyVideoSourceActivity" />
+             android:screenOrientation="locked"
+             android:label="PixelCopyVideoSourceActivity"/>
 
         <activity android:name="android.view.cts.PixelCopyGLProducerCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="PixelCopyGLProducerCtsActivity"/>
+             android:screenOrientation="locked"
+             android:label="PixelCopyGLProducerCtsActivity"/>
 
 
         <activity android:name="android.view.cts.PixelCopyViewProducerActivity"
-                  android:label="PixelCopyViewProducerActivity"
-                  android:screenOrientation="portrait"
-                  android:rotationAnimation="jumpcut"
-                  android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
-                  android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" />
+             android:label="PixelCopyViewProducerActivity"
+             android:screenOrientation="portrait"
+             android:rotationAnimation="jumpcut"
+             android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+             android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"/>
 
         <activity android:name="android.view.cts.PixelCopyWideGamutViewProducerActivity"
-                  android:label="PixelCopyWideGamutViewProducerActivity"
-                  android:screenOrientation="portrait"
-                  android:rotationAnimation="jumpcut"
-                  android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
-                  android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
-                  android:colorMode="wideColorGamut" />
+             android:label="PixelCopyWideGamutViewProducerActivity"
+             android:screenOrientation="portrait"
+             android:rotationAnimation="jumpcut"
+             android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+             android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
+             android:colorMode="wideColorGamut"/>
 
         <activity android:name="android.view.cts.PixelCopyViewProducerDialogActivity"
-                  android:label="PixelCopyViewProducerDialogActivity"
-                  android:screenOrientation="portrait"
-                  android:rotationAnimation="jumpcut"
-                  android:theme="@android:style/Theme.Material.Dialog.NoActionBar"
-                  android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" />
+             android:label="PixelCopyViewProducerDialogActivity"
+             android:screenOrientation="portrait"
+             android:rotationAnimation="jumpcut"
+             android:theme="@android:style/Theme.Material.Dialog.NoActionBar"
+             android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"/>
 
         <activity android:name="android.view.cts.FocusFinderCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="FocusFinderCtsActivity">
+             android:screenOrientation="locked"
+             android:label="FocusFinderCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.GestureDetectorCtsActivity"
-                  android:label="GestureDetectorCtsActivity"
-                  android:screenOrientation="locked"
-                  android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
+             android:label="GestureDetectorCtsActivity"
+             android:screenOrientation="locked"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen"/>
 
         <activity android:name="android.view.cts.ScaleGestureDetectorCtsActivity"
-                  android:label="ScaleGestureDetectorCtsActivity"
-                  android:screenOrientation="locked"
-                  android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
+             android:label="ScaleGestureDetectorCtsActivity"
+             android:screenOrientation="locked"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen"/>
 
         <activity android:name="android.view.cts.DisplayRefreshRateCtsActivity"
-                  android:label="DisplayRefreshRateCtsActivity"/>
+             android:label="DisplayRefreshRateCtsActivity"/>
 
         <activity android:name="android.view.cts.MockActivity"
-                  android:label="MockActivity"
-                  android:screenOrientation="locked">
+             android:label="MockActivity"
+             android:screenOrientation="locked">
             <meta-data android:name="android.view.merge"
-                android:resource="@xml/merge" />
+                 android:resource="@xml/merge"/>
         </activity>
 
         <activity android:name="android.view.cts.MenuTestActivity"
-                  android:screenOrientation="locked"
-                  android:label="MenuTestActivity" />
+             android:screenOrientation="locked"
+             android:label="MenuTestActivity"/>
 
         <activity android:name="android.view.cts.MenuItemCtsActivity"
-                  android:theme="@android:style/Theme.Material.Light.NoActionBar"
-                  android:screenOrientation="locked"
-                  android:label="MenuItemCtsActivity" />
+             android:theme="@android:style/Theme.Material.Light.NoActionBar"
+             android:screenOrientation="locked"
+             android:label="MenuItemCtsActivity"/>
 
         <activity android:name="android.view.cts.ActionModeCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ActionModeCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ActionModeCtsActivity">
         </activity>
 
         <activity android:name="android.view.cts.ViewOverlayCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ViewOverlayCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ViewOverlayCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ViewGroupOverlayCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ViewGroupOverlayCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ViewGroupOverlayCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.SearchEventActivity"
-                  android:screenOrientation="locked"
-                  android:label="SearchEventActivity">
+             android:screenOrientation="locked"
+             android:label="SearchEventActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.CtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="CtsActivity">
+             android:screenOrientation="locked"
+             android:label="CtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ContentPaneCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ContentPaneCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ContentPaneCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.LongPressBackActivity"
-                  android:screenOrientation="locked"
-                  android:label="LongPressBackActivity">
+             android:screenOrientation="locked"
+             android:label="LongPressBackActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.KeyEventInjectionActivity"
-                  android:label="KeyEventInjectionActivity">
+             android:label="KeyEventInjectionActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"
-            android:screenOrientation="locked"
-            android:theme="@style/WhiteBackgroundTheme">
+             android:screenOrientation="locked"
+             android:theme="@style/WhiteBackgroundTheme"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivityWithResource"
-                  android:screenOrientation="locked"
-                  android:theme="@style/WhiteBackgroundTheme">
+             android:screenOrientation="locked"
+             android:theme="@style/WhiteBackgroundTheme"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
-                 android:foregroundServiceType="mediaProjection"
-                 android:enabled="true">
+             android:foregroundServiceType="mediaProjection"
+             android:enabled="true">
         </service>
 
         <activity android:name="android.view.cts.HoverCtsActivity"
-                  android:screenOrientation="locked">
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.TooltipActivity"
-                  android:screenOrientation="locked"
-                  android:label="TooltipActivity">
+             android:screenOrientation="locked"
+             android:label="TooltipActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.PointerCaptureCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="PointerCaptureCtsActivity">
+             android:screenOrientation="locked"
+             android:label="PointerCaptureCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.DefaultFocusHighlightCtsActivity">
+        <activity android:name="android.view.cts.DefaultFocusHighlightCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.InputEventInterceptTestActivity">
+        <activity android:name="android.view.cts.InputEventInterceptTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.TouchDelegateTestActivity">
+        <activity android:name="android.view.cts.InputDeviceKeyLayoutMapTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.ViewSourceLayoutTestActivity">
+        <activity android:name="android.view.cts.TouchDelegateTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.SystemGestureExclusionActivity">
+        <activity android:name="android.view.cts.ViewSourceLayoutTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.view.cts.SystemGestureExclusionActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ViewAnimationMatrixActivity"
-                  android:theme="@android:style/Theme.Material.Light.NoActionBar">
+             android:theme="@android:style/Theme.Material.Light.NoActionBar"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.ViewUnbufferedTestActivity">
+        <activity android:name="android.view.cts.ViewUnbufferedTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name="android.view.textclassifier.cts.CtsTextClassifierService"
-            android:exported="true"
-            android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE">
+        <service android:name="android.view.textclassifier.cts.CtsTextClassifierService"
+             android:exported="true"
+             android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE">
             <intent-filter>
                 <action android:name="android.service.textclassifier.TextClassifierService"/>
             </intent-filter>
@@ -400,10 +440,10 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.view.cts"
-                     android:label="CTS tests of android.view">
+         android:targetPackage="android.view.cts"
+         android:label="CTS tests of android.view">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/view/jni/Android.bp b/tests/tests/view/jni/Android.bp
index 61932fc..2885bfe 100644
--- a/tests/tests/view/jni/Android.bp
+++ b/tests/tests/view/jni/Android.bp
@@ -22,8 +22,10 @@
 
     srcs: [
         "CtsViewJniOnLoad.cpp",
+        "android_view_cts_AInputNativeTest.cpp",
         "android_view_cts_ASurfaceControlTest.cpp",
         "android_view_cts_ChoreographerNativeTest.cpp",
+        "android_view_cts_InputDeviceKeyLayoutMapTest.cpp",
     ],
 
     shared_libs: [
diff --git a/tests/tests/view/jni/CtsViewJniOnLoad.cpp b/tests/tests/view/jni/CtsViewJniOnLoad.cpp
index a6f50ca..2d4b3d7 100644
--- a/tests/tests/view/jni/CtsViewJniOnLoad.cpp
+++ b/tests/tests/view/jni/CtsViewJniOnLoad.cpp
@@ -20,6 +20,9 @@
 
 extern int register_android_view_cts_ASurfaceControlTest(JNIEnv *);
 extern int register_android_view_cts_ChoreographerNativeTest(JNIEnv* env);
+extern int register_android_view_cts_AKeyEventNativeTest(JNIEnv *env);
+extern int register_android_view_cts_AMotionEventNativeTest(JNIEnv *env);
+extern int register_android_view_cts_InputDeviceKeyLayoutMapTest(JNIEnv *env);
 
 jint JNI_OnLoad(JavaVM *vm, void *) {
     JNIEnv *env = NULL;
@@ -32,5 +35,14 @@
     if (register_android_view_cts_ChoreographerNativeTest(env)) {
         return JNI_ERR;
     }
+    if (register_android_view_cts_AKeyEventNativeTest(env)) {
+        return JNI_ERR;
+    }
+    if (register_android_view_cts_AMotionEventNativeTest(env)) {
+        return JNI_ERR;
+    }
+    if (register_android_view_cts_InputDeviceKeyLayoutMapTest(env)) {
+        return JNI_ERR;
+    }
     return JNI_VERSION_1_4;
 }
diff --git a/tests/tests/view/jni/android_view_cts_AInputNativeTest.cpp b/tests/tests/view/jni/android_view_cts_AInputNativeTest.cpp
new file mode 100644
index 0000000..87670c4
--- /dev/null
+++ b/tests/tests/view/jni/android_view_cts_AInputNativeTest.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AInputNativeTest"
+
+#include <jni.h>
+#include <jniAssert.h>
+#include <math.h>
+#include <array>
+#include <cinttypes>
+#include <string>
+
+#include <android/input.h>
+#include <android/log.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace {
+
+static struct MotionEventMethodId {
+    jmethodID getDownTime;
+    jmethodID getEventTime;
+    jmethodID getMetaState;
+    jmethodID getAction;
+    jmethodID getPointerCount;
+    jmethodID getRawX;
+    jmethodID getRawY;
+} gMotionEventMethodIds;
+
+static struct KeyEventMethodId {
+    jmethodID getDownTime;
+    jmethodID getEventTime;
+    jmethodID getAction;
+    jmethodID getKeyCode;
+} gKeyEventMethodIds;
+
+static constexpr int64_t NS_PER_MS = 1000000LL;
+
+void nativeMotionEventTest(JNIEnv *env, jclass /* clazz */, jobject obj) {
+    const AInputEvent *event = AMotionEvent_fromJava(env, obj);
+    jint action = env->CallIntMethod(obj, gMotionEventMethodIds.getAction);
+    jlong downTime = env->CallLongMethod(obj, gMotionEventMethodIds.getDownTime) * NS_PER_MS;
+    jlong eventTime = env->CallLongMethod(obj, gMotionEventMethodIds.getEventTime) * NS_PER_MS;
+    jint metaState = env->CallIntMethod(obj, gMotionEventMethodIds.getMetaState);
+    jint pointerCount = env->CallIntMethod(obj, gMotionEventMethodIds.getPointerCount);
+
+    ASSERT(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION, "Wrong event type %d.",
+           AInputEvent_getType(event));
+
+    ASSERT(action == AMotionEvent_getAction(event), "Wrong action %d not equal to %d",
+           AMotionEvent_getAction(event), action);
+
+    ASSERT(downTime == AMotionEvent_getDownTime(event),
+           "Wrong downTime %" PRId64 " not equal to %" PRId64, AMotionEvent_getDownTime(event),
+           downTime);
+
+    ASSERT(eventTime == AMotionEvent_getEventTime(event),
+           "Wrong eventTime %" PRId64 " not equal to %" PRId64, AMotionEvent_getEventTime(event),
+           eventTime);
+
+    ASSERT(metaState == AMotionEvent_getMetaState(event), "Wrong metaState %d not equal to %d",
+           AMotionEvent_getMetaState(event), metaState);
+
+    ASSERT(AMotionEvent_getPointerCount(event) == pointerCount,
+           "Wrong pointer count %zu not equal to %d", AMotionEvent_getPointerCount(event),
+           pointerCount);
+
+    for (int i = 0; i < pointerCount; i++) {
+        jfloat rawX = env->CallFloatMethod(obj, gMotionEventMethodIds.getRawX, i);
+        jfloat rawY = env->CallFloatMethod(obj, gMotionEventMethodIds.getRawY, i);
+        ASSERT(fabs(rawX - AMotionEvent_getRawX(event, i)) == 0.0f, "Point X:%f not same as %f",
+               AMotionEvent_getRawX(event, i), rawX);
+
+        ASSERT(fabs(rawY - AMotionEvent_getRawY(event, i)) == 0.0f, "Point Y:%f not same as %f",
+               AMotionEvent_getRawY(event, i), rawY);
+    }
+    AInputEvent_release(event);
+}
+
+void nativeKeyEventTest(JNIEnv *env, jclass /* clazz */, jobject obj) {
+    const AInputEvent *event = AKeyEvent_fromJava(env, obj);
+    jint action = env->CallIntMethod(obj, gKeyEventMethodIds.getAction);
+    jlong downTime = env->CallLongMethod(obj, gKeyEventMethodIds.getDownTime) * NS_PER_MS;
+    jlong eventTime = env->CallLongMethod(obj, gKeyEventMethodIds.getEventTime) * NS_PER_MS;
+    jint keyCode = env->CallIntMethod(obj, gKeyEventMethodIds.getKeyCode);
+
+    ASSERT(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY, "Wrong event type %d.",
+           AInputEvent_getType(event));
+
+    ASSERT(action == AKeyEvent_getAction(event), "Wrong action %d not equal to %d",
+           AKeyEvent_getAction(event), action);
+
+    ASSERT(downTime == AKeyEvent_getDownTime(event),
+           "Wrong downTime %" PRId64 " not equal to %" PRId64, AKeyEvent_getDownTime(event),
+           downTime);
+
+    ASSERT(eventTime == AKeyEvent_getEventTime(event),
+           "Wrong eventTime %" PRId64 " not equal to %" PRId64, AKeyEvent_getEventTime(event),
+           eventTime);
+
+    ASSERT(keyCode == AKeyEvent_getKeyCode(event), "Wrong keyCode %d not equal to %d",
+           AKeyEvent_getAction(event), action);
+
+    AInputEvent_release(event);
+}
+
+const std::array<JNINativeMethod, 1> JNI_METHODS_MOTION = {{
+        {"nativeMotionEventTest", "(Landroid/view/MotionEvent;)V", (void *)nativeMotionEventTest},
+}};
+
+const std::array<JNINativeMethod, 1> JNI_METHODS_KEY = {{
+        {"nativeKeyEventTest", "(Landroid/view/KeyEvent;)V", (void *)nativeKeyEventTest},
+}};
+
+} // anonymous namespace
+
+jint register_android_view_cts_AMotionEventNativeTest(JNIEnv *env) {
+    jclass clazz = env->FindClass("android/view/MotionEvent");
+    gMotionEventMethodIds.getAction = env->GetMethodID(clazz, "getAction", "()I");
+    gMotionEventMethodIds.getMetaState = env->GetMethodID(clazz, "getMetaState", "()I");
+    gMotionEventMethodIds.getDownTime = env->GetMethodID(clazz, "getDownTime", "()J");
+    gMotionEventMethodIds.getEventTime = env->GetMethodID(clazz, "getEventTime", "()J");
+    gMotionEventMethodIds.getPointerCount = env->GetMethodID(clazz, "getPointerCount", "()I");
+    gMotionEventMethodIds.getRawX = env->GetMethodID(clazz, "getRawX", "(I)F");
+    gMotionEventMethodIds.getRawY = env->GetMethodID(clazz, "getRawY", "(I)F");
+    jclass clazzTest = env->FindClass("android/view/cts/MotionEventTest");
+    return env->RegisterNatives(clazzTest, JNI_METHODS_MOTION.data(), JNI_METHODS_MOTION.size());
+}
+
+jint register_android_view_cts_AKeyEventNativeTest(JNIEnv *env) {
+    jclass clazz = env->FindClass("android/view/KeyEvent");
+    gKeyEventMethodIds.getAction = env->GetMethodID(clazz, "getAction", "()I");
+    gKeyEventMethodIds.getKeyCode = env->GetMethodID(clazz, "getKeyCode", "()I");
+    gKeyEventMethodIds.getDownTime = env->GetMethodID(clazz, "getDownTime", "()J");
+    gKeyEventMethodIds.getEventTime = env->GetMethodID(clazz, "getEventTime", "()J");
+    jclass clazzTest = env->FindClass("android/view/cts/KeyEventTest");
+    return env->RegisterNatives(clazzTest, JNI_METHODS_KEY.data(), JNI_METHODS_KEY.size());
+}
diff --git a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
index 3bc8890..6ea343e 100644
--- a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
@@ -33,32 +33,11 @@
 
 #include <errno.h>
 #include <jni.h>
+#include <jniAssert.h>
 #include <time.h>
 
 namespace {
 
-// Raises a java exception
-static void fail(JNIEnv* env, const char* format, ...) {
-    va_list args;
-
-    va_start(args, format);
-    char* msg;
-    vasprintf(&msg, format, args);
-    va_end(args);
-
-    jclass exClass;
-    const char* className = "java/lang/AssertionError";
-    exClass = env->FindClass(className);
-    env->ThrowNew(exClass, msg);
-    free(msg);
-}
-
-#define ASSERT(condition, format, args...) \
-    if (!(condition)) {                    \
-        fail(env, format, ##args);         \
-        return;                            \
-    }
-
 #define NANOS_PER_SECOND 1000000000LL
 int64_t systemTime() {
     struct timespec time;
diff --git a/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp b/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp
new file mode 100644
index 0000000..4d2c810
--- /dev/null
+++ b/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <android/input.h>
+
+#include <jni.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sstream>
+
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <map>
+#include <unordered_map>
+#include <vector>
+
+#define LOG_TAG "InputDeviceKeyLayoutMapTest"
+
+namespace android {
+
+// Loads Generic.kl file and returns it as a std::map from scancode to key code.
+std::map<int, std::string> loadGenericKl(std::string genericKl) {
+    std::map<int, std::string> result;
+    std::istringstream ssFile(genericKl);
+
+    for (std::string line; std::getline(ssFile, line);) {
+        if (line.empty() || line[0] == '#') {
+            // Skip the comment lines
+            continue;
+        }
+
+        std::string type, code, label, flags;
+        std::istringstream ssLine(line);
+        ssLine >> type >> code >> label >> flags;
+
+        // Skip non-key mappings.
+        if (type != "key") {
+            continue;
+        }
+
+        // Skip HID usage keys.
+        if (code == "usage") {
+            continue;
+        }
+
+        // Skip keys with flags like "FUNCTION"
+        if (!flags.empty()) {
+            continue;
+        }
+
+        result.emplace(std::stoi(code), label);
+    }
+
+    return result;
+}
+
+static jobject android_view_cts_nativeLoadKeyLayout(JNIEnv* env, jclass, jstring genericKl) {
+    ScopedUtfChars keyLayout(env, genericKl);
+    if (keyLayout.c_str() == nullptr) {
+        return nullptr;
+    }
+    std::map<int, std::string> map = loadGenericKl(keyLayout.c_str());
+
+    ScopedLocalRef<jclass> hashMapClazz(env, env->FindClass("java/util/HashMap"));
+
+    jmethodID hashMapConstructID = env->GetMethodID(hashMapClazz.get(), "<init>", "()V");
+
+    jmethodID hashMapPutID =
+            env->GetMethodID(hashMapClazz.get(), "put",
+                             "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+
+    ScopedLocalRef<jclass> integerClazz(env, env->FindClass("java/lang/Integer"));
+
+    jmethodID integerConstructID = env->GetMethodID(integerClazz.get(), "<init>", "(I)V");
+
+    jobject keyLayoutMap = env->NewObject(hashMapClazz.get(), hashMapConstructID);
+
+    for (const auto& [key, label] : map) {
+        env->CallObjectMethod(keyLayoutMap, hashMapPutID, env->NewStringUTF(label.c_str()),
+                              env->NewObject(integerClazz.get(), integerConstructID, key));
+    }
+    return keyLayoutMap;
+}
+
+} // namespace android
+
+static JNINativeMethod gMethods[] = {
+        {"nativeLoadKeyLayout", "(Ljava/lang/String;)Ljava/util/Map;",
+         (void*)android::android_view_cts_nativeLoadKeyLayout},
+};
+
+int register_android_view_cts_InputDeviceKeyLayoutMapTest(JNIEnv* env) {
+    jclass clazz = env->FindClass("android/view/cts/InputDeviceKeyLayoutMapTest");
+    return env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/tests/view/jni/jniAssert.h b/tests/tests/view/jni/jniAssert.h
new file mode 100644
index 0000000..eaf9695
--- /dev/null
+++ b/tests/tests/view/jni/jniAssert.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+// Raises a java exception
+static void fail(JNIEnv *env, const char *format, ...) {
+    va_list args;
+
+    va_start(args, format);
+    char *msg;
+    vasprintf(&msg, format, args);
+    va_end(args);
+
+    jclass exClass;
+    const char *className = "java/lang/AssertionError";
+    exClass = env->FindClass(className);
+    env->ThrowNew(exClass, msg);
+    free(msg);
+}
+
+#define ASSERT(condition, format, args...) \
+    if (!(condition)) {                    \
+        fail(env, format, ##args);         \
+        return;                            \
+    }
diff --git a/tests/tests/view/res/raw/Generic.kl b/tests/tests/view/res/raw/Generic.kl
new file mode 100644
index 0000000..7c28cbf
--- /dev/null
+++ b/tests/tests/view/res/raw/Generic.kl
@@ -0,0 +1,447 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Generic key layout file for full alphabetic US English PC style external keyboards.
+#
+# This file is intentionally very generic and is intended to support a broad range of keyboards.
+# Do not edit the generic key layout to support a specific keyboard; instead, create
+# a new key layout file with the required keyboard configuration.
+#
+
+key 1     ESCAPE
+key 2     1
+key 3     2
+key 4     3
+key 5     4
+key 6     5
+key 7     6
+key 8     7
+key 9     8
+key 10    9
+key 11    0
+key 12    MINUS
+key 13    EQUALS
+key 14    DEL
+key 15    TAB
+key 16    Q
+key 17    W
+key 18    E
+key 19    R
+key 20    T
+key 21    Y
+key 22    U
+key 23    I
+key 24    O
+key 25    P
+key 26    LEFT_BRACKET
+key 27    RIGHT_BRACKET
+key 28    ENTER
+key 29    CTRL_LEFT
+key 30    A
+key 31    S
+key 32    D
+key 33    F
+key 34    G
+key 35    H
+key 36    J
+key 37    K
+key 38    L
+key 39    SEMICOLON
+key 40    APOSTROPHE
+key 41    GRAVE
+key 42    SHIFT_LEFT
+key 43    BACKSLASH
+key 44    Z
+key 45    X
+key 46    C
+key 47    V
+key 48    B
+key 49    N
+key 50    M
+key 51    COMMA
+key 52    PERIOD
+key 53    SLASH
+key 54    SHIFT_RIGHT
+key 55    NUMPAD_MULTIPLY
+key 56    ALT_LEFT
+key 57    SPACE
+key 58    CAPS_LOCK
+key 59    F1
+key 60    F2
+key 61    F3
+key 62    F4
+key 63    F5
+key 64    F6
+key 65    F7
+key 66    F8
+key 67    F9
+key 68    F10
+key 69    NUM_LOCK
+key 70    SCROLL_LOCK
+key 71    NUMPAD_7
+key 72    NUMPAD_8
+key 73    NUMPAD_9
+key 74    NUMPAD_SUBTRACT
+key 75    NUMPAD_4
+key 76    NUMPAD_5
+key 77    NUMPAD_6
+key 78    NUMPAD_ADD
+key 79    NUMPAD_1
+key 80    NUMPAD_2
+key 81    NUMPAD_3
+key 82    NUMPAD_0
+key 83    NUMPAD_DOT
+# key 84 (undefined)
+key 85    ZENKAKU_HANKAKU
+key 86    BACKSLASH
+key 87    F11
+key 88    F12
+key 89    RO
+# key 90 "KEY_KATAKANA"
+# key 91 "KEY_HIRAGANA"
+key 92    HENKAN
+key 93    KATAKANA_HIRAGANA
+key 94    MUHENKAN
+key 95    NUMPAD_COMMA
+key 96    NUMPAD_ENTER
+key 97    CTRL_RIGHT
+key 98    NUMPAD_DIVIDE
+key 99    SYSRQ
+key 100   ALT_RIGHT
+# key 101 "KEY_LINEFEED"
+key 102   MOVE_HOME
+key 103   DPAD_UP
+key 104   PAGE_UP
+key 105   DPAD_LEFT
+key 106   DPAD_RIGHT
+key 107   MOVE_END
+key 108   DPAD_DOWN
+key 109   PAGE_DOWN
+key 110   INSERT
+key 111   FORWARD_DEL
+# key 112 "KEY_MACRO"
+key 113   VOLUME_MUTE
+key 114   VOLUME_DOWN
+key 115   VOLUME_UP
+key 116   POWER
+key 117   NUMPAD_EQUALS
+# key 118 "KEY_KPPLUSMINUS"
+key 119   BREAK
+# key 120 (undefined)
+key 121   NUMPAD_COMMA
+key 122   KANA
+key 123   EISU
+key 124   YEN
+key 125   META_LEFT
+key 126   META_RIGHT
+key 127   MENU
+key 128   MEDIA_STOP
+# key 129 "KEY_AGAIN"
+# key 130 "KEY_PROPS"
+# key 131 "KEY_UNDO"
+# key 132 "KEY_FRONT"
+key 133   COPY
+# key 134 "KEY_OPEN"
+key 135   PASTE
+# key 136 "KEY_FIND"
+key 137   CUT
+# key 138 "KEY_HELP"
+key 139   MENU
+key 140   CALCULATOR
+# key 141 "KEY_SETUP"
+key 142   SLEEP
+key 143   WAKEUP
+# key 144 "KEY_FILE"
+# key 145 "KEY_SENDFILE"
+# key 146 "KEY_DELETEFILE"
+# key 147 "KEY_XFER"
+# key 148 "KEY_PROG1"
+# key 149 "KEY_PROG2"
+key 150   EXPLORER
+# key 151 "KEY_MSDOS"
+key 152   POWER
+# key 153 "KEY_DIRECTION"
+# key 154 "KEY_CYCLEWINDOWS"
+key 155   ENVELOPE
+key 156   BOOKMARK
+# key 157 "KEY_COMPUTER"
+key 158   BACK
+key 159   FORWARD
+key 160   MEDIA_CLOSE
+key 161   MEDIA_EJECT
+key 162   MEDIA_EJECT
+key 163   MEDIA_NEXT
+key 164   MEDIA_PLAY_PAUSE
+key 165   MEDIA_PREVIOUS
+key 166   MEDIA_STOP
+key 167   MEDIA_RECORD
+key 168   MEDIA_REWIND
+key 169   CALL
+# key 170 "KEY_ISO"
+key 171   MUSIC
+key 172   HOME
+key 173   REFRESH
+# key 174 "KEY_EXIT"
+# key 175 "KEY_MOVE"
+# key 176 "KEY_EDIT"
+key 177   PAGE_UP
+key 178   PAGE_DOWN
+key 179   NUMPAD_LEFT_PAREN
+key 180   NUMPAD_RIGHT_PAREN
+# key 181 "KEY_NEW"
+# key 182 "KEY_REDO"
+# key 183   F13
+# key 184   F14
+# key 185   F15
+# key 186   F16
+# key 187   F17
+# key 188   F18
+# key 189   F19
+# key 190   F20
+# key 191   F21
+# key 192   F22
+# key 193   F23
+# key 194   F24
+# key 195 (undefined)
+# key 196 (undefined)
+# key 197 (undefined)
+# key 198 (undefined)
+# key 199 (undefined)
+key 200   MEDIA_PLAY
+key 201   MEDIA_PAUSE
+# key 202 "KEY_PROG3"
+# key 203 "KEY_PROG4"
+# key 204 (undefined)
+# key 205 "KEY_SUSPEND"
+# key 206 "KEY_CLOSE"
+key 207   MEDIA_PLAY
+key 208   MEDIA_FAST_FORWARD
+# key 209 "KEY_BASSBOOST"
+# key 210 "KEY_PRINT"
+# key 211 "KEY_HP"
+key 212   CAMERA
+key 213   MUSIC
+# key 214 "KEY_QUESTION"
+key 215   ENVELOPE
+# key 216 "KEY_CHAT"
+key 217   SEARCH
+# key 218 "KEY_CONNECT"
+# key 219 "KEY_FINANCE"
+# key 220 "KEY_SPORT"
+# key 221 "KEY_SHOP"
+# key 222 "KEY_ALTERASE"
+# key 223 "KEY_CANCEL"
+key 224   BRIGHTNESS_DOWN
+key 225   BRIGHTNESS_UP
+key 226   HEADSETHOOK
+
+key 256   BUTTON_1
+key 257   BUTTON_2
+key 258   BUTTON_3
+key 259   BUTTON_4
+key 260   BUTTON_5
+key 261   BUTTON_6
+key 262   BUTTON_7
+key 263   BUTTON_8
+key 264   BUTTON_9
+key 265   BUTTON_10
+key 266   BUTTON_11
+key 267   BUTTON_12
+key 268   BUTTON_13
+key 269   BUTTON_14
+key 270   BUTTON_15
+key 271   BUTTON_16
+
+key 288   BUTTON_1
+key 289   BUTTON_2
+key 290   BUTTON_3
+key 291   BUTTON_4
+key 292   BUTTON_5
+key 293   BUTTON_6
+key 294   BUTTON_7
+key 295   BUTTON_8
+key 296   BUTTON_9
+key 297   BUTTON_10
+key 298   BUTTON_11
+key 299   BUTTON_12
+key 300   BUTTON_13
+key 301   BUTTON_14
+key 302   BUTTON_15
+key 303   BUTTON_16
+
+
+key 304   BUTTON_A
+key 305   BUTTON_B
+key 306   BUTTON_C
+key 307   BUTTON_X
+key 308   BUTTON_Y
+key 309   BUTTON_Z
+key 310   BUTTON_L1
+key 311   BUTTON_R1
+key 312   BUTTON_L2
+key 313   BUTTON_R2
+key 314   BUTTON_SELECT
+key 315   BUTTON_START
+key 316   BUTTON_MODE
+key 317   BUTTON_THUMBL
+key 318   BUTTON_THUMBR
+
+
+# key 352 "KEY_OK"
+key 353   DPAD_CENTER
+# key 354 "KEY_GOTO"
+# key 355 "KEY_CLEAR"
+# key 356 "KEY_POWER2"
+# key 357 "KEY_OPTION"
+# key 358 "KEY_INFO"
+# key 359 "KEY_TIME"
+# key 360 "KEY_VENDOR"
+# key 361 "KEY_ARCHIVE"
+key 362   GUIDE
+# key 363 "KEY_CHANNEL"
+# key 364 "KEY_FAVORITES"
+# key 365 "KEY_EPG"
+key 366   DVR
+# key 367 "KEY_MHP"
+# key 368 "KEY_LANGUAGE"
+# key 369 "KEY_TITLE"
+key 370   CAPTIONS
+# key 371 "KEY_ANGLE"
+# key 372 "KEY_ZOOM"
+# key 373 "KEY_MODE"
+# key 374 "KEY_KEYBOARD"
+# key 375 "KEY_SCREEN"
+# key 376 "KEY_PC"
+key 377   TV
+# key 378 "KEY_TV2"
+# key 379 "KEY_VCR"
+# key 380 "KEY_VCR2"
+# key 381 "KEY_SAT"
+# key 382 "KEY_SAT2"
+# key 383 "KEY_CD"
+# key 384 "KEY_TAPE"
+# key 385 "KEY_RADIO"
+# key 386 "KEY_TUNER"
+# key 387 "KEY_PLAYER"
+# key 388 "KEY_TEXT"
+# key 389 "KEY_DVD"
+# key 390 "KEY_AUX"
+# key 391 "KEY_MP3"
+# key 392 "KEY_AUDIO"
+# key 393 "KEY_VIDEO"
+# key 394 "KEY_DIRECTORY"
+# key 395 "KEY_LIST"
+# key 396 "KEY_MEMO"
+key 397   CALENDAR
+key 398   PROG_RED
+key 399   PROG_GREEN
+key 400   PROG_YELLOW
+key 401   PROG_BLUE
+key 402   CHANNEL_UP
+key 403   CHANNEL_DOWN
+# key 404 "KEY_FIRST"
+key 405   LAST_CHANNEL
+# key 406 "KEY_AB"
+# key 407 "KEY_NEXT"
+# key 408 "KEY_RESTART"
+# key 409 "KEY_SLOW"
+# key 410 "KEY_SHUFFLE"
+# key 411 "KEY_BREAK"
+# key 412 "KEY_PREVIOUS"
+# key 413 "KEY_DIGITS"
+# key 414 "KEY_TEEN"
+# key 415 "KEY_TWEN"
+
+key 429   CONTACTS
+
+# key 448 "KEY_DEL_EOL"
+# key 449 "KEY_DEL_EOS"
+# key 450 "KEY_INS_LINE"
+# key 451 "KEY_DEL_LINE"
+
+
+key 464   FUNCTION
+key 465   ESCAPE            FUNCTION
+key 466   F1                FUNCTION
+key 467   F2                FUNCTION
+key 468   F3                FUNCTION
+key 469   F4                FUNCTION
+key 470   F5                FUNCTION
+key 471   F6                FUNCTION
+key 472   F7                FUNCTION
+key 473   F8                FUNCTION
+key 474   F9                FUNCTION
+key 475   F10               FUNCTION
+key 476   F11               FUNCTION
+key 477   F12               FUNCTION
+key 478   1                 FUNCTION
+key 479   2                 FUNCTION
+key 480   D                 FUNCTION
+key 481   E                 FUNCTION
+key 482   F                 FUNCTION
+key 483   S                 FUNCTION
+key 484   B                 FUNCTION
+
+
+# key 497 KEY_BRL_DOT1
+# key 498 KEY_BRL_DOT2
+# key 499 KEY_BRL_DOT3
+# key 500 KEY_BRL_DOT4
+# key 501 KEY_BRL_DOT5
+# key 502 KEY_BRL_DOT6
+# key 503 KEY_BRL_DOT7
+# key 504 KEY_BRL_DOT8
+
+key 522   STAR
+key 523   POUND
+key 580   APP_SWITCH
+key 582   VOICE_ASSIST
+# Linux KEY_ASSISTANT
+key 583   ASSIST
+
+# Keys defined by HID usages
+key usage 0x0c0067 WINDOW
+key usage 0x0c006F BRIGHTNESS_UP
+key usage 0x0c0070 BRIGHTNESS_DOWN
+key usage 0x0c0173 MEDIA_AUDIO_TRACK
+
+# Joystick and game controller axes.
+# Axes that are not mapped will be assigned generic axis numbers by the input subsystem.
+axis 0x00 X
+axis 0x01 Y
+axis 0x02 Z
+axis 0x03 RX
+axis 0x04 RY
+axis 0x05 RZ
+axis 0x06 THROTTLE
+axis 0x07 RUDDER
+axis 0x08 WHEEL
+axis 0x09 GAS
+axis 0x0a BRAKE
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+# LEDs
+led 0x00 NUM_LOCK
+led 0x01 CAPS_LOCK
+led 0x02 SCROLL_LOCK
+led 0x03 COMPOSE
+led 0x04 KANA
+led 0x05 SLEEP
+led 0x06 SUSPEND
+led 0x07 MUTE
+led 0x08 MISC
+led 0x09 MAIL
+led 0x0a CHARGING
diff --git a/tests/tests/view/res/raw/google_gamepad_register.json b/tests/tests/view/res/raw/google_gamepad_register.json
new file mode 100644
index 0000000..6d398d0
--- /dev/null
+++ b/tests/tests/view/res/raw/google_gamepad_register.json
@@ -0,0 +1,15 @@
+{
+    "id": 1,
+    "type": "uinput",
+    "command": "register",
+    "name": "Gamepad FF (USB Test)",
+    "vid": 0x18d1,
+    "pid": 0xabcd,
+    "bus": "usb",
+    "configuration":[
+        {"type":100, "data":[1, 21]},  // UI_SET_EVBIT : EV_KEY and EV_FF
+        {"type":101, "data":[11, 2, 3, 4]},   // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3
+        {"type":107, "data":[80]}    //  UI_SET_FFBIT : FF_RUMBLE
+    ],
+    "ff_effects_max" : 1
+}
diff --git a/tests/tests/view/res/raw/google_gamepad_vibratortests.json b/tests/tests/view/res/raw/google_gamepad_vibratortests.json
new file mode 100644
index 0000000..4f13fbf
--- /dev/null
+++ b/tests/tests/view/res/raw/google_gamepad_vibratortests.json
@@ -0,0 +1,14 @@
+[
+    {
+      "id": 1,
+      "durations" : [1000],
+      "amplitudes" : [192]
+    },
+
+    {
+      "id": 1,
+      "durations" : [2000, 2000, 2000, 2000, 2000],
+      "amplitudes" : [16, 32, 64, 128, 255]
+    }
+
+]
diff --git a/tests/tests/view/src/android/view/cts/InputCallback.java b/tests/tests/view/src/android/view/cts/InputCallback.java
new file mode 100644
index 0000000..1512406
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/InputCallback.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import android.view.KeyEvent;
+
+public interface InputCallback {
+    void onKeyEvent(KeyEvent ev);
+}
diff --git a/tests/tests/view/src/android/view/cts/InputDeviceKeyLayoutMapTest.java b/tests/tests/view/src/android/view/cts/InputDeviceKeyLayoutMapTest.java
new file mode 100644
index 0000000..ebbfbb5
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/InputDeviceKeyLayoutMapTest.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.cts.input.InputJsonParser;
+import com.android.cts.input.UinputDevice;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * CTS test case for generic.kl key layout mapping.
+ * This test utilize uinput command line tool to create a test device, and configure the virtual
+ * device to have all keys need to be tested. The JSON format input for device configuration
+ * and EV_KEY injection will be created directly from this test for uinput command.
+ * Keep res/raw/Generic.kl in sync with framework/base/data/keyboards/Generic.kl, this file
+ * will be loaded and parsed in this test, looping through all key labels and the corresponding
+ * EV_KEY code, injecting the KEY_UP and KEY_DOWN event to uinput, then verify the KeyEvent
+ * delivered to test application view. Except meta control keys and special keys not delivered
+ * to apps, all key codes in generic.kl will be verified.
+ *
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceKeyLayoutMapTest {
+    private static final String TAG = "InputDeviceKeyLayoutMapTest";
+    private static final String LABEL_PREFIX = "KEYCODE_";
+    private static final int DEVICE_ID = 1;
+    private static final int EV_SYN = 0;
+    private static final int EV_KEY = 1;
+    private static final int EV_KEY_DOWN = 1;
+    private static final int EV_KEY_UP = 0;
+    private static final int UI_SET_EVBIT = 100;
+    private static final int UI_SET_KEYBIT = 101;
+    private static final int GOOGLE_VENDOR_ID = 0x18d1;
+    private static final int GOOGLE_VIRTUAL_KEYBOARD_ID = 0x001f;
+
+    private Map<String, Integer> mKeyLayout;
+    private InputDeviceKeyLayoutMapTestActivity mActivity;
+    private Instrumentation mInstrumentation;
+    private UinputDevice mUinputDevice;
+    private final BlockingQueue<KeyEvent> mEvents = new LinkedBlockingQueue<>();
+    private InputListener mInputListener;
+    private int mMetaState;
+    private InputJsonParser mParser;
+
+    private static native Map<String, Integer> nativeLoadKeyLayout(String genericKeyLayout);
+
+    static {
+        System.loadLibrary("ctsview_jni");
+    }
+
+    @Rule
+    public ActivityTestRule<InputDeviceKeyLayoutMapTestActivity> mActivityRule =
+            new ActivityTestRule<>(InputDeviceKeyLayoutMapTestActivity.class);
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mActivity = mActivityRule.getActivity();
+        PollingCheck.waitFor(mActivity::hasWindowFocus);
+
+        mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+        mKeyLayout = nativeLoadKeyLayout(mParser.readRegisterCommand(R.raw.Generic));
+        mUinputDevice = new UinputDevice(mInstrumentation, DEVICE_ID,
+                createDeviceRegisterCommand());
+        mInputListener = new InputListener();
+        mActivityRule.getActivity().setInputCallback(mInputListener);
+        mMetaState = KeyEvent.META_NUM_LOCK_ON;
+    }
+
+    @After
+    public void tearDown() {
+        mUinputDevice.close();
+    }
+
+    private class InputListener implements InputCallback {
+        @Override
+        public void onKeyEvent(KeyEvent ev) {
+            try {
+                mEvents.put(new KeyEvent(ev));
+            } catch (InterruptedException ex) {
+                fail("interrupted while adding a KeyEvent to the queue");
+            }
+        }
+    }
+
+    /**
+     * Get a KeyEvent from event queue or timeout.
+     *
+     * @return KeyEvent delivered to test activity, null if timeout.
+     */
+    private KeyEvent getKeyEvent() {
+        try {
+            KeyEvent receivedKeyEvent = mEvents.poll(10, TimeUnit.SECONDS);
+            if (receivedKeyEvent == null) {
+                fail("Did not receive any key event");
+            }
+            return receivedKeyEvent;
+        } catch (InterruptedException e) {
+            throw new RuntimeException("unexpectedly interrupted while waiting for InputEvent", e);
+        }
+    }
+
+    /**
+     * Asserts that the application received a {@link android.view.KeyEvent} with the given
+     * metadata.
+     *
+     * If other KeyEvents are received by the application prior to the expected KeyEvent, or no
+     * KeyEvents are received within a reasonable amount of time, then this will throw an
+     * {@link AssertionError}.
+     *
+     * Only action, source, keyCode and metaState are being compared.
+     */
+    private void assertReceivedKeyEvent(@NonNull KeyEvent expectedKeyEvent) {
+        if (expectedKeyEvent.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
+            return;
+        }
+
+        KeyEvent receivedKeyEvent = getKeyEvent();
+        String log = "Expected " + expectedKeyEvent + " Received " + receivedKeyEvent;
+
+        assertEquals(log, expectedKeyEvent.getAction(), receivedKeyEvent.getAction());
+        assertEquals(log, expectedKeyEvent.getSource(), receivedKeyEvent.getSource());
+        assertEquals(log, expectedKeyEvent.getKeyCode(), receivedKeyEvent.getKeyCode());
+        assertEquals(log, expectedKeyEvent.getMetaState(), receivedKeyEvent.getMetaState());
+    }
+
+    /**
+     * Create the uinput device registration command, in JSON format of uinput commandline tool.
+     * Refer to {@link framework/base/cmds/uinput/README.md}
+     */
+    private String createDeviceRegisterCommand() {
+        JSONObject json = new JSONObject();
+        JSONArray arrayConfigs =  new JSONArray();
+        try {
+            json.put("id", DEVICE_ID);
+            json.put("type", "uinput");
+            json.put("command", "register");
+            json.put("name", "Virtual All Buttons Device (Test)");
+            json.put("vid", GOOGLE_VENDOR_ID);
+            json.put("pid", GOOGLE_VIRTUAL_KEYBOARD_ID);
+            json.put("bus", "bluetooth");
+
+            JSONObject jsonSetEvBit = new JSONObject();
+            JSONArray arraySetEvBit =  new JSONArray();
+            arraySetEvBit.put(EV_KEY);
+            jsonSetEvBit.put("type", UI_SET_EVBIT);
+            jsonSetEvBit.put("data", arraySetEvBit);
+            arrayConfigs.put(jsonSetEvBit);
+
+            // Configure device have all keys from key layout map.
+            JSONArray arraySetKeyBit = new JSONArray();
+            for (Map.Entry<String, Integer> entry : mKeyLayout.entrySet()) {
+                arraySetKeyBit.put(entry.getValue());
+            }
+            JSONObject jsonSetKeyBit = new JSONObject();
+            jsonSetKeyBit.put("type", UI_SET_KEYBIT);
+            jsonSetKeyBit.put("data", arraySetKeyBit);
+            arrayConfigs.put(jsonSetKeyBit);
+            json.put("configuration", arrayConfigs);
+        } catch (JSONException e) {
+            throw new RuntimeException(
+                    "Could not create JSON object");
+        }
+
+        return json.toString();
+    }
+
+    /**
+     * Update expected meta state for incoming key event.
+     * @param action KeyEvent.ACTION_DOWN or KeyEvent.ACTION_UP
+     * @param label Key label from key layout mapping definition
+     * @return updated meta state
+     */
+
+    private int updateMetaState(int action, String label) {
+
+        int metaState = 0;
+        int metaStateToggle = 0;
+        if (label.equals("CTRL_LEFT")) {
+            metaState = KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON;
+        }
+        if (label.equals("CTRL_RIGHT")) {
+            metaState = KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_RIGHT_ON;
+        }
+        if (label.equals("SHIFT_LEFT")) {
+            metaState = KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON;
+        }
+        if (label.equals("SHIFT_RIGHT")) {
+            metaState = KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_RIGHT_ON;
+        }
+        if (label.equals("ALT_LEFT")) {
+            metaState = KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON;
+        }
+        if (label.equals("ALT_RIGHT")) {
+            metaState = KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON;
+        }
+        if (label.equals("CAPS_LOCK")) {
+            metaStateToggle =  KeyEvent.META_CAPS_LOCK_ON;
+        }
+        if (label.equals("NUM_LOCK")) {
+            metaStateToggle =  KeyEvent.META_NUM_LOCK_ON;
+        }
+        if (label.equals("SCROLL_LOCK")) {
+            metaStateToggle =  KeyEvent.META_SCROLL_LOCK_ON;
+        }
+
+        if (action == KeyEvent.ACTION_DOWN) {
+            mMetaState |= metaState;
+        } else if (action == KeyEvent.ACTION_UP) {
+            mMetaState &= ~metaState;
+        }
+
+        if (action == KeyEvent.ACTION_UP) {
+            if ((mMetaState & metaStateToggle) == 0) {
+                mMetaState |= metaStateToggle;
+            } else {
+                mMetaState &= ~metaStateToggle;
+            }
+        }
+        return mMetaState;
+    }
+
+    /**
+     * Generate a key event from the key label and action.
+     * @param action KeyEvent.ACTION_DOWN or KeyEvent.ACTION_UP
+     * @param label Key label from key layout mapping definition
+     * @return KeyEvent expected to receive
+     */
+    private KeyEvent generateKeyEvent(int action, String label) {
+        int source = InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_GAMEPAD
+                | InputDevice.SOURCE_DPAD;
+        int keyCode = KeyEvent.keyCodeFromString(LABEL_PREFIX + label);
+        int metaState = updateMetaState(action, label);
+        // We will only check select fields of the KeyEvent. Times are not checked.
+        KeyEvent event = new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode,
+                /* repeat */ 0, metaState, /* deviceId */ 0, /* scanCode */ 0,
+                /* flags */ 0, source);
+
+        return event;
+    }
+
+    /**
+     * Simulate pressing a key.
+     * @param evKeyCode The key scan code
+     */
+    private void pressKey(int evKeyCode) {
+        int[] evCodesDown = new int[] {
+                EV_KEY, evKeyCode, EV_KEY_DOWN,
+                EV_SYN, 0, 0};
+        mUinputDevice.injectEvents(Arrays.toString(evCodesDown));
+
+        int[] evCodesUp = new int[] {
+                EV_KEY, evKeyCode, EV_KEY_UP,
+                EV_SYN, 0, 0 };
+        mUinputDevice.injectEvents(Arrays.toString(evCodesUp));
+    }
+
+    /**
+     * Check the initial global meta key state.
+     * @param label Key label from key layout mapping definition
+     * @param metaState The meta state that the meta key changes
+     */
+    private void checkMetaKeyState(String label, int metaState) {
+        int eveKeyCode = mKeyLayout.get(label);
+        pressKey(eveKeyCode);
+        // Get 2 key events for up and down.
+        KeyEvent downKeyEvent = getKeyEvent();
+        KeyEvent upKeyEvent = getKeyEvent();
+
+        if (upKeyEvent.getKeyCode() == KeyEvent.keyCodeFromString(label)
+                && upKeyEvent.getAction() == KeyEvent.ACTION_UP) {
+            mMetaState &= ~metaState;
+            mMetaState |= (upKeyEvent.getMetaState() & metaState);
+        }
+    }
+
+    /**
+     * Initialize NUM_LOCK, CAPS_LOCK, SCROLL_LOCK state as they are global meta state
+     */
+    private void initializeMetaKeysState() {
+        // Detect NUM_LOCK key state before test.
+        checkMetaKeyState("NUM_LOCK", KeyEvent.META_NUM_LOCK_ON);
+        // Detect CAPS_LOCK key state before test.
+        checkMetaKeyState("CAPS_LOCK", KeyEvent.META_CAPS_LOCK_ON);
+        // Detect CAPS_LOCK key state before test.
+        checkMetaKeyState("SCROLL_LOCK", KeyEvent.META_SCROLL_LOCK_ON);
+    }
+
+    @Test
+    public void testLayoutKeyEvents() {
+        final List<String> excludedKeys = Arrays.asList(
+                // Meta control keys.
+                "CAPS_LOCK", "NUM_LOCK", "SCROLL_LOCK", "META_LEFT", "META_RIGHT", "FUNCTION",
+                // KeyEvents not delivered to apps.
+                "APP_SWITCH", "SYSRQ", "ASSIST", "VOICE_ASSIST",
+                "HOME", "POWER", "SLEEP", "WAKEUP",
+                "BRIGHTNESS_UP", "BRIGHTNESS_DOWN");
+
+        initializeMetaKeysState();
+
+        for (Map.Entry<String, Integer> entry : mKeyLayout.entrySet()) {
+            String label = LABEL_PREFIX + entry.getKey();
+            int evKeyCode = entry.getValue();
+
+            if (excludedKeys.contains(label)) {
+                continue;
+            }
+
+            assertNotEquals(KeyEvent.keyCodeFromString(label), KeyEvent.KEYCODE_UNKNOWN);
+            // Press the key
+            pressKey(evKeyCode);
+            // Generate expected key down event and verify
+            KeyEvent expectedDownEvent = generateKeyEvent(KeyEvent.ACTION_DOWN,  label);
+            assertReceivedKeyEvent(expectedDownEvent);
+            // Generate expected key up event and verify
+            KeyEvent expectedUpEvent = generateKeyEvent(KeyEvent.ACTION_UP,  label);
+            assertReceivedKeyEvent(expectedUpEvent);
+        }
+    }
+
+}
diff --git a/tests/tests/view/src/android/view/cts/InputDeviceKeyLayoutMapTestActivity.java b/tests/tests/view/src/android/view/cts/InputDeviceKeyLayoutMapTestActivity.java
new file mode 100644
index 0000000..ecf2446
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/InputDeviceKeyLayoutMapTestActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+public class InputDeviceKeyLayoutMapTestActivity extends Activity {
+    private static final String TAG = "InputDeviceKeyLayoutMapTestActivity";
+
+    private InputCallback mInputCallback;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent ev) {
+        if (mInputCallback != null) {
+            mInputCallback.onKeyEvent(ev);
+        }
+        return true;
+    }
+
+    public void setInputCallback(InputCallback callback) {
+        mInputCallback = callback;
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/InputDeviceMultiDeviceKeyEventTest.java b/tests/tests/view/src/android/view/cts/InputDeviceMultiDeviceKeyEventTest.java
new file mode 100644
index 0000000..c0f1247
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/InputDeviceMultiDeviceKeyEventTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.hardware.input.InputManager;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.cts.input.UinputDevice;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * CTS test cases for multi device key events verification.
+ * This test utilize uinput command line tool to create multiple test devices, and configure the
+ * virtual device to have keys need to be tested. The JSON format input for device configuration
+ * and EV_KEY injection will be created directly from this test for uinput command.
+ * The test cases will inject evdev events from different virtual input devices and verify the
+ * received key events to verify the device Id, repeat count to be expected, as well as the key
+ * repeat behavior is consistently meeting expectations with multi devices.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceMultiDeviceKeyEventTest {
+    private static final String TAG = "InputDeviceMultiDeviceKeyEventTest";
+    private static final String LABEL_PREFIX = "KEYCODE_";
+    private static final int DEVICE_ID = 1;
+    private static final int EV_SYN = 0;
+    private static final int EV_KEY = 1;
+    private static final int EV_KEY_DOWN = 1;
+    private static final int EV_KEY_UP = 0;
+    private static final int UI_SET_EVBIT = 100;
+    private static final int UI_SET_KEYBIT = 101;
+    private static final int EV_KEY_CODE_1 = 2;
+    private static final int EV_KEY_CODE_2 = 3;
+    private static final int GOOGLE_VENDOR_ID = 0x18d1;
+    private static final int GOOGLE_VIRTUAL_KEYBOARD_ID = 0x001f;
+    private static final int NUM_DEVICES = 2;
+
+    private InputDeviceKeyLayoutMapTestActivity mActivity;
+    private Instrumentation mInstrumentation;
+    private InputManager mInputManager;
+    private UinputDevice[] mUinputDevices = new UinputDevice[NUM_DEVICES];
+    private int[] mInputManagerDeviceIds = new int[NUM_DEVICES];
+    private final BlockingQueue<KeyEvent> mEvents = new LinkedBlockingQueue<>();
+    private InputListener mInputListener;
+    private final int[] mEvKeys = {
+            EV_KEY_CODE_1,
+            EV_KEY_CODE_2
+    };
+
+    @Rule
+    public ActivityTestRule<InputDeviceKeyLayoutMapTestActivity> mActivityRule =
+            new ActivityTestRule<>(InputDeviceKeyLayoutMapTestActivity.class);
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        for (int i = 0; i < NUM_DEVICES; i++) {
+            final int jsonDeviceId = i + 1;
+            mUinputDevices[i] = new UinputDevice(mInstrumentation, jsonDeviceId,
+                createDeviceRegisterCommand(jsonDeviceId, mEvKeys));
+        }
+        mInputListener = new InputListener();
+        mActivityRule.getActivity().setInputCallback(mInputListener);
+
+        mInputManager = mInstrumentation.getContext().getSystemService(InputManager.class);
+        final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
+            final int index = inputDevice.getProductId() - GOOGLE_VIRTUAL_KEYBOARD_ID - 1;
+            if (inputDevice.getVendorId() == GOOGLE_VENDOR_ID
+                    && index >= 0 && index < NUM_DEVICES) {
+                mInputManagerDeviceIds[index] = inputDeviceId;
+            }
+        }
+    }
+
+    @After
+    public void tearDown() {
+        for (int i = 0; i < NUM_DEVICES; i++) {
+            mUinputDevices[i].close();
+        }
+    }
+
+    private class InputListener implements InputCallback {
+        @Override
+        public void onKeyEvent(KeyEvent ev) {
+            try {
+                mEvents.put(new KeyEvent(ev));
+            } catch (InterruptedException ex) {
+                fail("interrupted while adding a KeyEvent to the queue");
+            }
+        }
+    }
+
+    /**
+     * Create the uinput device registration command, in JSON format of uinput commandline tool.
+     * Refer to {@link framework/base/cmds/uinput/README.md}
+     */
+    private String createDeviceRegisterCommand(int deviceId, int[] keys) {
+        JSONObject json = new JSONObject();
+        JSONArray arrayConfigs =  new JSONArray();
+        try {
+            json.put("id", deviceId);
+            json.put("type", "uinput");
+            json.put("command", "register");
+            json.put("name", "Virtual All Buttons Device (Test)");
+            json.put("vid", GOOGLE_VENDOR_ID);
+            json.put("pid", GOOGLE_VIRTUAL_KEYBOARD_ID + deviceId);
+            json.put("bus", "bluetooth");
+
+            JSONObject jsonSetEvBit = new JSONObject();
+            JSONArray arraySetEvBit =  new JSONArray();
+            arraySetEvBit.put(EV_KEY);
+            jsonSetEvBit.put("type", UI_SET_EVBIT);
+            jsonSetEvBit.put("data", arraySetEvBit);
+            arrayConfigs.put(jsonSetEvBit);
+
+            // Configure device have all keys from key layout map.
+            JSONArray arraySetKeyBit = new JSONArray();
+            for (int i = 0; i < keys.length; i++) {
+                arraySetKeyBit.put(keys[i]);
+            }
+
+            JSONObject jsonSetKeyBit = new JSONObject();
+            jsonSetKeyBit.put("type", UI_SET_KEYBIT);
+            jsonSetKeyBit.put("data", arraySetKeyBit);
+            arrayConfigs.put(jsonSetKeyBit);
+            json.put("configuration", arrayConfigs);
+        } catch (JSONException e) {
+            throw new RuntimeException(
+                    "Could not create JSON object");
+        }
+
+        return json.toString();
+    }
+
+    /**
+     * Get a KeyEvent from event queue or timeout.
+     *
+     * @return KeyEvent delivered to test activity, null if timeout.
+     */
+    private KeyEvent getKeyEvent() {
+        try {
+            KeyEvent receivedKeyEvent = mEvents.poll(5, TimeUnit.SECONDS);
+            if (receivedKeyEvent == null) {
+                fail("Did not receive any key event");
+            }
+            return receivedKeyEvent;
+        } catch (InterruptedException e) {
+            throw new RuntimeException("unexpectedly interrupted while waiting for InputEvent", e);
+        }
+    }
+
+    private void assertNoKeyEvent() {
+        try {
+            KeyEvent receivedKeyEvent = mEvents.poll(5, TimeUnit.SECONDS);
+            assertNull(receivedKeyEvent);
+        } catch (InterruptedException e) {
+            throw new RuntimeException("unexpectedly interrupted while waiting for InputEvent", e);
+        }
+    }
+
+    /**
+     * Asserts that the application received a {@link android.view.KeyEvent} with the given
+     * metadata.
+     *
+     * If other KeyEvents are received by the application prior to the expected KeyEvent, or no
+     * KeyEvents are received within a reasonable amount of time, then this will throw an
+     * {@link AssertionError}.
+     *
+     * Only action, source, keyCode and metaState are being compared.
+     */
+    private void assertReceivedKeyEvent(@NonNull KeyEvent expectedKeyEvent) {
+        assertNotEquals(expectedKeyEvent.getKeyCode(), KeyEvent.KEYCODE_UNKNOWN);
+
+        KeyEvent receivedKeyEvent = getKeyEvent();
+        String log = "Expected " + expectedKeyEvent + " Received " + receivedKeyEvent;
+        assertEquals("DeviceId: " + log, expectedKeyEvent.getDeviceId(),
+                receivedKeyEvent.getDeviceId());
+        assertEquals("Action: " + log, expectedKeyEvent.getAction(),
+                receivedKeyEvent.getAction());
+        assertEquals("Source: " + log, expectedKeyEvent.getSource(),
+                receivedKeyEvent.getSource());
+        assertEquals("KeyCode: " + log, expectedKeyEvent.getKeyCode(),
+                receivedKeyEvent.getKeyCode());
+        assertEquals("RepeatCount: " + log, expectedKeyEvent.getRepeatCount(),
+                receivedKeyEvent.getRepeatCount());
+    }
+
+    /**
+     * Generate a key event from the key label and action.
+     * @param action KeyEvent.ACTION_DOWN or KeyEvent.ACTION_UP
+     * @param label Key label from key layout mapping definition
+     * @return KeyEvent expected to receive
+     */
+    private KeyEvent generateKeyEvent(int deviceId, int action, String label, int repeat) {
+        int source = InputDevice.SOURCE_KEYBOARD;
+        int keyCode = KeyEvent.keyCodeFromString(LABEL_PREFIX + label);
+        // We will only check select fields of the KeyEvent. Times are not checked.
+        KeyEvent event = new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode,
+                repeat, /* metaState */ 0, mInputManagerDeviceIds[deviceId], /* scanCode */ 0,
+                /* flags */ 0, source);
+
+        return event;
+    }
+
+    /**
+     * Simulate pressing a key.
+     * @param evKeyCode The key scan code
+     */
+    private void pressKeyDown(int deviceId, int evKeyCode) {
+        int[] evCodesDown = new int[] {
+                EV_KEY, evKeyCode, EV_KEY_DOWN,
+                EV_SYN, 0, 0};
+        mUinputDevices[deviceId].injectEvents(Arrays.toString(evCodesDown));
+    }
+
+    /**
+     * Simulate releasing a key.
+     * @param evKeyCode The key scan code
+     */
+    private void pressKeyUp(int deviceId, int evKeyCode) {
+        int[] evCodesUp = new int[] {
+                EV_KEY, evKeyCode, EV_KEY_UP,
+                EV_SYN, 0, 0 };
+        mUinputDevices[deviceId].injectEvents(Arrays.toString(evCodesUp));
+    }
+
+    private void assertKeyRepeat(int deviceId, String label, int repeat, int count) {
+        for (int i = 0; i < count; i++) {
+            KeyEvent expectedDownEvent = generateKeyEvent(deviceId,
+                    KeyEvent.ACTION_DOWN, label, repeat + i);
+            assertReceivedKeyEvent(expectedDownEvent);
+        }
+    }
+
+    private void assertKeyUp(int deviceId, String label) {
+        KeyEvent expectedUpEvent = generateKeyEvent(deviceId,
+                KeyEvent.ACTION_UP, label, /* repeat */ 0);
+        assertReceivedKeyEvent(expectedUpEvent);
+    }
+
+    @Test
+    public void testReceivesKeyRepeatFromTwoDevices() {
+        final String keyOne = "1";
+        // Press the key from device 0
+        pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 0
+        assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
+        // Press the key from device 1
+        pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 1
+        assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 0, /* count */ 10);
+    }
+
+    @Test
+    public void testReceivesKeyRepeatOnTwoKeysFromTwoDevices() {
+        final String keyOne = "1";
+        final String keyTwo = "2";
+        // Press the key 1 from device 0
+        pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 0
+        assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
+
+        // Press the key 2 from device 1
+        pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_2);
+        // KeyDown repeat driven by device 1
+        assertKeyRepeat(/* deviceId */ 1, keyTwo, /* repeat */ 0, /* count */ 10);
+
+        // Release the key 2 from device 1
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 1, EV_KEY_CODE_2);
+        assertKeyUp(/* deviceId */ 1, keyTwo);
+
+        // No key repeating anymore.
+        assertNoKeyEvent();
+
+        // Release the key 1 from device 0
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 0, EV_KEY_CODE_1);
+        assertKeyUp(/* deviceId */ 0, keyOne);
+    }
+
+    @Test
+    public void testKeyRepeatAfterStaleDeviceKeyUp() {
+        final String keyOne = "1";
+        // Press the key from device 0
+        pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 0
+        assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
+
+        // Press the key from device 1
+        pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 1
+        assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 0, /* count */ 10);
+
+        // Release the key from device 0
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 0, EV_KEY_CODE_1);
+        assertKeyUp(/* deviceId */ 0, keyOne);
+
+        // KeyDown kept repeating by device 1
+        assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 10, /* count */ 10);
+
+        // Release the key from device 1
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 1, EV_KEY_CODE_1);
+        assertKeyUp(/* deviceId */ 1, keyOne);
+    }
+
+    @Test
+    public void testKeyRepeatStopsAfterRepeatingKeyUp() {
+        final String keyOne = "1";
+        // Press the key from device 0
+        pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 0
+        assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
+
+        // Press the key from device 1
+        pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 1
+        assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 0, /* count */ 10);
+
+        // Release the key from device 1
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 1, EV_KEY_CODE_1);
+        assertKeyUp(/* deviceId */ 1, keyOne);
+
+        // No key repeating anymore.
+        assertNoKeyEvent();
+
+        // Release the key from device 0
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 0, EV_KEY_CODE_1);
+        assertKeyUp(/* deviceId */ 0, keyOne);
+    }
+
+}
diff --git a/tests/tests/view/src/android/view/cts/InputDeviceVibratorTest.java b/tests/tests/view/src/android/view/cts/InputDeviceVibratorTest.java
new file mode 100644
index 0000000..4137d3d
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/InputDeviceVibratorTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.hardware.input.InputManager;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.Log;
+import android.view.InputDevice;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.input.InputJsonParser;
+import com.android.cts.input.UinputDevice;
+import com.android.cts.input.UinputResultData;
+import com.android.cts.input.UinputVibratorTestData;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test {@link android.view.InputDevice} vibrator functionality.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceVibratorTest {
+    private static final String TAG = "InputDeviceVibratorTest";
+    private InputManager mInputManager;
+    private UinputDevice mUinputDevice;
+    private InputJsonParser mParser;
+    private Instrumentation mInstrumentation;
+    private Vibrator mVibrator;
+    private int mDeviceId;
+
+    /**
+     * Get a vibrator from input device with specified Vendor Id and Product Id.
+     * @param vid Vendor Id
+     * @param pid Product Id
+     * @return Vibrator object in specified InputDevice
+     */
+    private Vibrator getVibrator(int vid, int pid) {
+        final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
+            Vibrator vibrator = inputDevice.getVibrator();
+            if (vibrator.hasVibrator() && inputDevice.getVendorId() == vid
+                    && inputDevice.getProductId() == pid) {
+                Log.i(TAG, "Input device: " + inputDeviceId + " VendorId: "
+                        + inputDevice.getVendorId() + " ProductId: " + inputDevice.getProductId());
+                return vibrator;
+            }
+        }
+        return null;
+    }
+
+    @Before
+    public void setup() {
+        final int resourceId = R.raw.google_gamepad_register;
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
+        assertNotNull(mInputManager);
+        mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+        mDeviceId = mParser.readDeviceId(resourceId);
+        String registerCommand = mParser.readRegisterCommand(resourceId);
+        mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId, registerCommand);
+        mVibrator = getVibrator(mParser.readVendorId(resourceId),
+                mParser.readProductId(resourceId));
+        assertTrue(mVibrator != null);
+    }
+
+    @After
+    public void tearDown() {
+        mUinputDevice.close();
+    }
+
+    public void testInputVibratorEvents(int resourceId) {
+        final List<UinputVibratorTestData> tests = mParser.getUinputVibratorTestData(resourceId);
+
+        for (UinputVibratorTestData test : tests) {
+            assertTrue(test.durations.size() == test.amplitudes.size());
+            assertTrue(test.durations.size() > 0);
+
+            final long timeoutMills;
+            final long totalVibrations = test.durations.size();
+            final VibrationEffect effect;
+            if (test.durations.size() == 1) {
+                long duration = test.durations.get(0);
+                int amplitude = test.amplitudes.get(0);
+                effect = VibrationEffect.createOneShot(duration, amplitude);
+                // Set timeout to be 2 times of the effect duration.
+                timeoutMills = duration * 2;
+            } else {
+                long[] durations = test.durations.stream().mapToLong(Long::longValue).toArray();
+                int[] amplitudes = test.amplitudes.stream().mapToInt(Integer::intValue).toArray();
+                effect = VibrationEffect.createWaveform(
+                    durations, amplitudes, -1);
+                // Set timeout to be 2 times of the effect total duration.
+                timeoutMills = Arrays.stream(durations).sum() * 2;
+            }
+
+            // Start vibration
+            mVibrator.vibrate(effect);
+            final long startTime = SystemClock.elapsedRealtime();
+            List<UinputResultData> results = new ArrayList<>();
+            int vibrationCount = 0;
+
+            while (vibrationCount < totalVibrations
+                    && SystemClock.elapsedRealtime() - startTime < timeoutMills) {
+                SystemClock.sleep(1000);
+                try {
+                    results = mUinputDevice.getResults(mDeviceId, "vibrating");
+                    if (results.size() < totalVibrations) {
+                        continue;
+                    }
+                    vibrationCount = 0;
+                    for (int i = 0; i < results.size(); i++) {
+                        UinputResultData result = results.get(i);
+                        if (result.reason.equals("vibrating") && result.deviceId == mDeviceId
+                                && (result.status > 0)) {
+                            vibrationCount++;
+                        }
+                    }
+                }  catch (IOException ex) {
+                    throw new RuntimeException("Could not get JSON results from HidDevice");
+                }
+            }
+            assertTrue(vibrationCount == totalVibrations);
+        }
+    }
+
+    @Test
+    public void testInputVibrator() {
+        testInputVibratorEvents(R.raw.google_gamepad_vibratortests);
+    }
+
+}
diff --git a/tests/tests/view/src/android/view/cts/KeyEventTest.java b/tests/tests/view/src/android/view/cts/KeyEventTest.java
index 2f7e1a5..516b3e1 100644
--- a/tests/tests/view/src/android/view/cts/KeyEventTest.java
+++ b/tests/tests/view/src/android/view/cts/KeyEventTest.java
@@ -59,6 +59,12 @@
     private long mDownTime;
     private long mEventTime;
 
+    private static native void nativeKeyEventTest(KeyEvent event);
+
+    static {
+        System.loadLibrary("ctsview_jni");
+    }
+
     @Before
     public void setup() {
         mKeyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0);
@@ -586,6 +592,23 @@
     }
 
     @Test
+    public void testIsMediaSessionKey() {
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_PLAY));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_PAUSE));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MUTE));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_HEADSETHOOK));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_STOP));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_NEXT));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_PREVIOUS));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_REWIND));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_RECORD));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD));
+
+        assertFalse(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_0));
+    }
+
+    @Test
     public void testGetMatch() {
         // Our default key event is down + 0, so we expect getMatch to return our '0' character
         assertEquals('0', mKeyEvent.getMatch(new char[] { '0', '1', '2' }));
@@ -795,6 +818,13 @@
                 KeyEvent.keyCodeFromString(Integer.toString(KeyEvent.LAST_KEYCODE + 1)));
     }
 
+    @Test
+    public void testNativeConverter() {
+        mKeyEvent = new KeyEvent(mDownTime, mEventTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A,
+                1, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_TOUCHSCREEN);
+        nativeKeyEventTest(mKeyEvent);
+    }
+
     // Parcel a KeyEvent, then create a new KeyEvent from this parcel. Return the new KeyEvent
     private KeyEvent parcelUnparcel(KeyEvent keyEvent) {
         Parcel parcel = Parcel.obtain();
diff --git a/tests/tests/view/src/android/view/cts/MotionEventTest.java b/tests/tests/view/src/android/view/cts/MotionEventTest.java
index 14ccf8b..08fda91 100644
--- a/tests/tests/view/src/android/view/cts/MotionEventTest.java
+++ b/tests/tests/view/src/android/view/cts/MotionEventTest.java
@@ -72,6 +72,12 @@
     private static final float DELTA               = 0.01f;
     private static final float RAW_COORD_TOLERANCE = 0.001f;
 
+    private static native void nativeMotionEventTest(MotionEvent event);
+
+    static {
+        System.loadLibrary("ctsview_jni");
+    }
+
     @Before
     public void setup() {
         mDownTime = SystemClock.uptimeMillis();
@@ -980,4 +986,11 @@
         assertEquals(MotionEvent.CLASSIFICATION_NONE, mMotionEvent1.getClassification());
         assertEquals(MotionEvent.CLASSIFICATION_NONE, mMotionEvent2.getClassification());
     }
+
+    @Test
+    public void testNativeConverter() {
+        final MotionEvent event = MotionEvent.obtain(mDownTime, mEventTime,
+                MotionEvent.ACTION_MOVE, X_3F, Y_4F, META_STATE);
+        nativeMotionEventTest(event);
+    }
 }
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 3f9f563..25eccdf 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -79,6 +79,7 @@
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MotionEvent;
+import android.view.OnReceiveContentCallback;
 import android.view.PointerIcon;
 import android.view.SoundEffectConstants;
 import android.view.TouchDelegate;
@@ -1252,6 +1253,20 @@
     }
 
     @Test
+    public void testSetOnReceiveContentCallback() {
+        View view = new View(mActivity);
+        assertNull(view.getOnReceiveContentCallback());
+
+        @SuppressWarnings("unchecked")
+        OnReceiveContentCallback<View> callback = mock(OnReceiveContentCallback.class);
+        view.setOnReceiveContentCallback(callback);
+        assertSame(callback, view.getOnReceiveContentCallback());
+
+        view.setOnReceiveContentCallback(null);
+        assertNull(view.getOnReceiveContentCallback());
+    }
+
+    @Test
     public void testGetContextMenuInfo() {
         MockView view = new MockView(mActivity);
 
diff --git a/tests/tests/voiceinteraction/AndroidManifest.xml b/tests/tests/voiceinteraction/AndroidManifest.xml
index 393d7a8..10f9038 100644
--- a/tests/tests/voiceinteraction/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/AndroidManifest.xml
@@ -16,37 +16,38 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.voiceinteraction.cts">
+     package="android.voiceinteraction.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION"/>
 
     <application>
-      <uses-library android:name="android.test.runner" />
+      <uses-library android:name="android.test.runner"/>
 
       <activity android:name="TestStartActivity"
-                android:label="Voice Interaction Target">
+           android:label="Voice Interaction Target"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.TEST_START_ACTIVITY" />
-              <category android:name="android.intent.category.LAUNCHER" />
-              <category android:name="android.intent.category.DEFAULT" />
+              <action android:name="android.intent.action.TEST_START_ACTIVITY"/>
+              <category android:name="android.intent.category.LAUNCHER"/>
+              <category android:name="android.intent.category.DEFAULT"/>
           </intent-filter>
       </activity>
       <activity android:name="TestLocalInteractionActivity"
-                android:label="Local Interaction Activity">
+           android:label="Local Interaction Activity"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.TEST_LOCAL_INTERACTION_ACTIVITY" />
+              <action android:name="android.intent.action.TEST_LOCAL_INTERACTION_ACTIVITY"/>
           </intent-filter>
       </activity>
       <receiver android:name="VoiceInteractionTestReceiver"
-              android:exported="true" />
+           android:exported="true"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.voiceinteraction.cts"
-                     android:label="CTS tests of android.voiceinteraction">
+         android:targetPackage="android.voiceinteraction.cts"
+         android:label="CTS tests of android.voiceinteraction">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/voiceinteraction/testapp/AndroidManifest.xml b/tests/tests/voiceinteraction/testapp/AndroidManifest.xml
index c683c66..020ee80 100644
--- a/tests/tests/voiceinteraction/testapp/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/testapp/AndroidManifest.xml
@@ -16,36 +16,37 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.voiceinteraction.testapp">
+     package="android.voiceinteraction.testapp">
 
     <application>
-      <uses-library android:name="android.test.runner" />
+      <uses-library android:name="android.test.runner"/>
 
       <activity android:name="TestApp"
-                android:label="Voice Interaction Test App"
-                android:theme="@android:style/Theme.DeviceDefault">
+           android:label="Voice Interaction Test App"
+           android:theme="@android:style/Theme.DeviceDefault"
+           android:exported="true">
           <intent-filter>
               <action android:name="android.intent.action.VIEW"/>
-              <category android:name="android.intent.category.DEFAULT" />
-              <category android:name="android.intent.category.BROWSABLE" />
-              <category android:name="android.intent.category.VOICE" />
-              <data android:scheme="https" />
-              <data android:host="android.voiceinteraction.testapp" />
-              <data android:path="/TestApp" />
+              <category android:name="android.intent.category.DEFAULT"/>
+              <category android:name="android.intent.category.BROWSABLE"/>
+              <category android:name="android.intent.category.VOICE"/>
+              <data android:scheme="https"/>
+              <data android:host="android.voiceinteraction.testapp"/>
+              <data android:path="/TestApp"/>
           </intent-filter>
       </activity>
 
        <activity android:name=".DirectActionsActivity"
-                android:label="Direct actions activity"
-                android:exported="true">
+            android:label="Direct actions activity"
+            android:exported="true">
           <intent-filter>
               <action android:name="android.intent.action.VIEW"/>
-              <category android:name="android.intent.category.DEFAULT" />
-              <category android:name="android.intent.category.BROWSABLE" />
-              <data android:scheme="https" />
-              <data android:host="android.voiceinteraction.testapp" />
-              <data android:path="/DirectActionsActivity" />
-              <category android:name="android.intent.category.VOICE" />
+              <category android:name="android.intent.category.DEFAULT"/>
+              <category android:name="android.intent.category.BROWSABLE"/>
+              <data android:scheme="https"/>
+              <data android:host="android.voiceinteraction.testapp"/>
+              <data android:path="/DirectActionsActivity"/>
+              <category android:name="android.intent.category.VOICE"/>
           </intent-filter>
         </activity>
 
diff --git a/tests/tests/voicesettings/AndroidManifest.xml b/tests/tests/voicesettings/AndroidManifest.xml
index 8be0b80..ba50604 100644
--- a/tests/tests/voicesettings/AndroidManifest.xml
+++ b/tests/tests/voicesettings/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
  * Copyright (C) 2015 The Android Open Source Project
  *
@@ -15,31 +16,31 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.voicesettings.cts">
+     package="android.voicesettings.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name=".BroadcastTestStartActivity"
-                  android:label="The Target Activity for VoiceSettings CTS Test">
+             android:label="The Target Activity for VoiceSettings CTS Test"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_START_ACTIVITY_ZEN_MODE" />
-                <action android:name="android.intent.action.TEST_START_ACTIVITY_AIRPLANE_MODE" />
-                <action android:name="android.intent.action.TEST_START_ACTIVITY_BATTERYSAVER_MODE" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.TEST_START_ACTIVITY_ZEN_MODE"/>
+                <action android:name="android.intent.action.TEST_START_ACTIVITY_AIRPLANE_MODE"/>
+                <action android:name="android.intent.action.TEST_START_ACTIVITY_BATTERYSAVER_MODE"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.voicesettings.cts"
-                     android:label="CTS tests of android.voicesettings">
+         android:targetPackage="android.voicesettings.cts"
+         android:label="CTS tests of android.voicesettings">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/voicesettings/TEST_MAPPING b/tests/tests/voicesettings/TEST_MAPPING
new file mode 100644
index 0000000..fe855df
--- /dev/null
+++ b/tests/tests/voicesettings/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsVoiceSettingsTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/voicesettings/service/AndroidManifest.xml b/tests/tests/voicesettings/service/AndroidManifest.xml
index 13671b6..af106f4 100644
--- a/tests/tests/voicesettings/service/AndroidManifest.xml
+++ b/tests/tests/voicesettings/service/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
  * Copyright (C) 2015 The Android Open Source Project
  *
@@ -15,51 +16,54 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.voicesettings.service">
+     package="android.voicesettings.service">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <service android:name=".MainInteractionService"
-                android:label="CTS test voice interaction service"
-                android:permission="android.permission.BIND_VOICE_INTERACTION"
-                android:process=":interactor"
-                android:exported="true">
+             android:label="CTS test voice interaction service"
+             android:permission="android.permission.BIND_VOICE_INTERACTION"
+             android:process=":interactor"
+             android:exported="true">
             <meta-data android:name="android.voice_interaction"
-                       android:resource="@xml/interaction_service" />
+                 android:resource="@xml/interaction_service"/>
             <intent-filter>
-                <action android:name="android.service.voice.VoiceInteractionService" />
+                <action android:name="android.service.voice.VoiceInteractionService"/>
             </intent-filter>
         </service>
-        <activity android:name=".VoiceInteractionMain" >
+        <activity android:name=".VoiceInteractionMain"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIMAIN_ZEN_MODE_ON" />
-                <action android:name="android.intent.action.VIMAIN_ZEN_MODE_OFF" />
-                <action android:name="android.intent.action.VIMAIN_AIRPLANE_MODE_ON" />
-                <action android:name="android.intent.action.VIMAIN_AIRPLANE_MODE_OFF" />
-                <action android:name="android.intent.action.VIMAIN_BATTERYSAVER_MODE_ON" />
-                <action android:name="android.intent.action.VIMAIN_BATTERYSAVER_MODE_OFF" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.VIMAIN_ZEN_MODE_ON"/>
+                <action android:name="android.intent.action.VIMAIN_ZEN_MODE_OFF"/>
+                <action android:name="android.intent.action.VIMAIN_AIRPLANE_MODE_ON"/>
+                <action android:name="android.intent.action.VIMAIN_AIRPLANE_MODE_OFF"/>
+                <action android:name="android.intent.action.VIMAIN_BATTERYSAVER_MODE_ON"/>
+                <action android:name="android.intent.action.VIMAIN_BATTERYSAVER_MODE_OFF"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <activity android:name=".SettingsActivity"
-                  android:label="Voice Interaction Settings">
+             android:label="Voice Interaction Settings"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <service android:name=".MainInteractionSessionService"
-                android:permission="android.permission.BIND_VOICE_INTERACTION"
-                android:process=":session">
+             android:permission="android.permission.BIND_VOICE_INTERACTION"
+             android:process=":session">
         </service>
         <service android:name=".MainRecognitionService"
-                android:label="CTS Voice Recognition Service">
+             android:label="CTS Voice Recognition Service"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.speech.RecognitionService" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.speech.RecognitionService"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
-            <meta-data android:name="android.speech" android:resource="@xml/recognition_service" />
+            <meta-data android:name="android.speech"
+                 android:resource="@xml/recognition_service"/>
         </service>
     </application>
 </manifest>
-
diff --git a/tests/tests/webkit/AndroidManifest.xml b/tests/tests/webkit/AndroidManifest.xml
index e7de9bf..ed0e92f 100644
--- a/tests/tests/webkit/AndroidManifest.xml
+++ b/tests/tests/webkit/AndroidManifest.xml
@@ -16,69 +16,74 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.webkit.cts">
+     package="android.webkit.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
     <!-- Note: we must provide INTERNET permission for
-     ServiceWorkerWebSettingsTest#testBlockNetworkLoads -->
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <application android:maxRecents="1" android:usesCleartextTraffic="true">
+             ServiceWorkerWebSettingsTest#testBlockNetworkLoads -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <application android:maxRecents="1"
+         android:usesCleartextTraffic="true">
         <provider android:name="android.webkit.cts.MockContentProvider"
-                  android:exported="true"
-                  android:authorities="android.webkit.cts.MockContentProvider" />
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+             android:exported="true"
+             android:authorities="android.webkit.cts.MockContentProvider"/>
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
 
         <activity android:name="android.webkit.cts.CookieSyncManagerCtsActivity"
-            android:label="CookieSyncManagerCtsActivity"
-            android:screenOrientation="nosensor">
+             android:label="CookieSyncManagerCtsActivity"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.webkit.cts.WebViewCtsActivity"
-            android:label="WebViewCtsActivity"
-            android:screenOrientation="nosensor">
+             android:label="WebViewCtsActivity"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.webkit.cts.WebViewStartupCtsActivity"
-            android:label="WebViewStartupCtsActivity"
-            android:screenOrientation="nosensor">
+             android:label="WebViewStartupCtsActivity"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <service android:name="android.webkit.cts.TestProcessServiceA"
-                 android:process=":testprocessA"
-                 android:exported="false" />
+             android:process=":testprocessA"
+             android:exported="false"/>
 
         <service android:name="android.webkit.cts.TestProcessServiceB"
-                 android:process=":testprocessB"
-                 android:exported="false" />
+             android:process=":testprocessB"
+             android:exported="false"/>
 
         <!-- Specify a preloaded font list to ensure that this doesn't interfere
-             with the operation of the renderer process (as in b/70968451)
-         -->
-        <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts" />
+                         with the operation of the renderer process (as in b/70968451)
+                     -->
+        <meta-data android:name="preloaded_fonts"
+             android:resource="@array/preloaded_fonts"/>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.webkit.cts"
-                     android:label="CTS tests of android.webkit">
+         android:targetPackage="android.webkit.cts"
+         android:label="CTS tests of android.webkit">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/widget/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml
index c30c71c..b822759 100644
--- a/tests/tests/widget/AndroidManifest.xml
+++ b/tests/tests/widget/AndroidManifest.xml
@@ -16,615 +16,685 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.widget.cts"
-    android:targetSandboxVersion="2">
+     package="android.widget.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
 
     <application android:label="Android TestCase"
-            android:icon="@drawable/size_48x48"
-            android:maxRecents="1"
-            android:multiArch="true"
-            android:name="android.widget.cts.MockApplication"
-            android:supportsRtl="true"
-            android:theme="@android:style/Theme.Material.Light.DarkActionBar">
+         android:icon="@drawable/size_48x48"
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:name="android.widget.cts.MockApplication"
+         android:supportsRtl="true"
+         android:theme="@android:style/Theme.Material.Light.DarkActionBar">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.widget.cts.EmptyCtsActivity"
-                  android:label="EmptyCtsActivity">
+             android:label="EmptyCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.AbsoluteLayoutCtsActivity"
-                  android:label="AbsoluteLayoutCtsActivity">
+             android:label="AbsoluteLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TwoLineListItemCtsActivity"
-            android:label="TwoLineListItemCtsActivity">
+             android:label="TwoLineListItemCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ViewFlipperCtsActivity"
-            android:label="ViewFlipperCtsActivity">
+             android:label="ViewFlipperCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.HorizontalScrollViewCtsActivity"
-            android:label="HorizontalScrollViewCtsActivity">
+             android:label="HorizontalScrollViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.SlidingDrawerCtsActivity"
-            android:label="SlidingDrawerCtsActivity">
+             android:label="SlidingDrawerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.DigitalClockCtsActivity"
-            android:label="DigitalClockCtsActivity">
+             android:label="DigitalClockCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ImageViewCtsActivity"
-                  android:label="ImageViewCtsActivity">
+             android:label="ImageViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ImageSwitcherCtsActivity"
-                  android:label="ImageSwitcherCtsActivity">
+             android:label="ImageSwitcherCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TextSwitcherCtsActivity"
-                  android:label="TextSwitcherCtsActivity">
+             android:label="TextSwitcherCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.SwitchCtsActivity"
-                  android:label="SwitchCtsActivity">
+             android:label="SwitchCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.SpinnerCtsActivity"
-                  android:label="SpinnerCtsActivity">
+             android:label="SpinnerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ToolbarCtsActivity"
-                  android:theme="@android:style/Theme.Material.Light.NoActionBar"
-                  android:label="ToolbarCtsActivity">
+             android:theme="@android:style/Theme.Material.Light.NoActionBar"
+             android:label="ToolbarCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ToolbarWithMarginsCtsActivity"
-                  android:theme="@android:style/Theme.Material.Light.NoActionBar"
-                  android:label="ToolbarWithMarginsCtsActivity">
+             android:theme="@android:style/Theme.Material.Light.NoActionBar"
+             android:label="ToolbarWithMarginsCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ActionMenuViewCtsActivity"
-                  android:label="ActionMenuViewCtsActivity">
+             android:label="ActionMenuViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.SeekBarCtsActivity"
-            android:label="SeekBarCtsActivity">
+             android:label="SeekBarCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ScrollViewCtsActivity"
-            android:label="ScrollViewCtsActivity">
+             android:label="ScrollViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.FrameLayoutCtsActivity"
-            android:label="FrameLayoutCtsActivity">
+             android:label="FrameLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.LinearLayoutCtsActivity"
-            android:label="LinearLayoutCtsActivity">
+             android:label="LinearLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.GridLayoutCtsActivity"
-            android:label="GridLayoutCtsActivity">
+             android:label="GridLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.LayoutDirectionCtsActivity"
-            android:label="LayoutDirectionCtsActivity">
+             android:label="LayoutDirectionCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.AbsSeekBarCtsActivity"
-            android:label="AbsSeekBarCtsActivity">
+             android:label="AbsSeekBarCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ProgressBarCtsActivity"
-            android:label="ProgressBarCtsActivity">
+             android:label="ProgressBarCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ChronometerCtsActivity"
-            android:label="ChronometerCtsActivity">
+             android:label="ChronometerCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.MediaControllerCtsActivity"
-            android:label="MediaControllerCtsActivity">
+             android:label="MediaControllerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.RatingBarCtsActivity"
-            android:label="RatingBarCtsActivity">
+             android:label="RatingBarCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.RemoteViewsCtsActivity"
-            android:label="RemoteViewsCtsActivity">
+             android:label="RemoteViewsCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ExpandableListBasic"
-                  android:label="ExpandableListBasic">
+             android:label="ExpandableListBasic"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ExpandableList"
-                  android:label="ExpandableList">
+             android:label="ExpandableList"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.CtsActivity"
-            android:label="CtsActivity">
+             android:label="CtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ExpandableListWithHeaders"
-            android:label="ExpandableListWithHeaders">
+             android:label="ExpandableListWithHeaders"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.GalleryCtsActivity"
-            android:label="GalleryCtsActivity">
+             android:label="GalleryCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.PopupWindowCtsActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize"
-            android:label="PopupWindowCtsActivity"
-            android:theme="@style/Theme.PopupWindowCtsActivity">
+             android:configChanges="keyboardHidden|orientation|screenSize"
+             android:label="PopupWindowCtsActivity"
+             android:theme="@style/Theme.PopupWindowCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.PopupMenuCtsActivity"
-                  android:label="PopupMenuCtsActivity">
+             android:label="PopupMenuCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ListPopupWindowCtsActivity"
-                  android:label="ListPopupWindowCtsActivity"
-                  android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="ListPopupWindowCtsActivity"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ListViewCtsActivity"
-                  android:label="ListViewCtsActivity">
+             android:label="ListViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ListViewFixedCtsActivity"
-                  android:label="ListViewFixedCtsActivity">
+             android:label="ListViewFixedCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TextClockCtsActivity"
-                  android:label="TextClockCtsActivity"
-                  android:screenOrientation="nosensor"
-                  android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="TextClockCtsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TextViewCtsActivity"
                   android:label="TextViewCtsActivity"
                   android:screenOrientation="locked"
+                  android:exported="true"
                   android:windowSoftInputMode="stateAlwaysHidden">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.EditTextCtsActivity"
-                  android:label="EditTextCtsActivity"
-                  android:screenOrientation="nosensor"
-                  android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="EditTextCtsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.DialerFilterCtsActivity"
-            android:label="DialerFilterCtsActivity">
+             android:label="DialerFilterCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.MultiAutoCompleteTextViewCtsActivity"
-            android:label="MultiAutoCompleteTextView Test Activity">
+             android:label="MultiAutoCompleteTextView Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.VideoViewCtsActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize"
-            android:label="VideoViewCtsActivity">
+             android:configChanges="keyboardHidden|orientation|screenSize"
+             android:label="VideoViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.AutoCompleteCtsActivity"
-            android:label="AutoCompleteCtsActivity"
-            android:screenOrientation="nosensor"
-            android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="AutoCompleteCtsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.widget.cts.ViewAnimatorCtsActivity" android:label="ViewAnimatorCtsActivity">
+        <activity android:name="android.widget.cts.ViewAnimatorCtsActivity"
+             android:label="ViewAnimatorCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.GridViewCtsActivity"
-            android:label="GridViewCtsActivity">
+             android:label="GridViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.RelativeLayoutCtsActivity"
-            android:label="RelativeLayoutCtsActivity">
+             android:label="RelativeLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.FrameLayoutCtsActivity"
-            android:label="FrameLayoutCtsActivity">
+             android:label="FrameLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.AdapterViewCtsActivity"
-            android:label="AdapterViewCtsActivity">
+             android:label="AdapterViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.CheckedTextViewCtsActivity"
-            android:label="CheckedTextViewCtsActivity"/>
+             android:label="CheckedTextViewCtsActivity"/>
 
         <activity android:name="android.widget.cts.TableCtsActivity"
-            android:label="TableCtsActivity">
+             android:label="TableCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TabHostCtsActivity"
-            android:label="TabHostCtsActivity">
+             android:label="TabHostCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ZoomButtonCtsActivity"
-            android:label="ZoomButtonCtsActivity">
+             android:label="ZoomButtonCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.DatePickerDialogCtsActivity"
-                  android:label="DatePickerDialogCtsActivity">
+             android:label="DatePickerDialogCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.CalendarViewCtsActivity"
-                  android:label="CalendarViewCtsActivity">
+             android:label="CalendarViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.DatePickerCtsActivity"
-                  android:label="DatePickerCtsActivity">
+             android:label="DatePickerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.SearchViewCtsActivity"
-                  android:label="SearchViewCtsActivity">
+             android:label="SearchViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ImageButtonCtsActivity"
-                  android:label="ImageButtonCtsActivity">
+             android:label="ImageButtonCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.NumberPickerCtsActivity"
-                  android:label="NumberPickerCtsActivity">
+             android:label="NumberPickerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.CheckBoxCtsActivity"
-                  android:label="CheckBoxCtsActivity">
+             android:label="CheckBoxCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.CompoundButtonCtsActivity"
-                  android:label="CompoundButtonCtsActivity">
+             android:label="CompoundButtonCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.RadioButtonCtsActivity"
-                  android:label="RadioButtonCtsActivity">
+             android:label="RadioButtonCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ToggleButtonCtsActivity"
-                  android:label="ToggleButtonCtsActivity">
+             android:label="ToggleButtonCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TimePickerCtsActivity"
-                  android:label="TimePickerCtsActivity">
+             android:label="TimePickerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.BackwardNavigationCtsActivity"
-            android:label="BackwardNavigationCtsActivity">
+             android:label="BackwardNavigationCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.RadioGroupCtsActivity"
-                  android:label="RadioGroupCtsActivity">
+             android:label="RadioGroupCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.Activity"
-                  android:label="Activity"
-                  android:theme="@style/WidgetAttributeTestTheme">
+             android:label="Activity"
+             android:theme="@style/WidgetAttributeTestTheme"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.ActivityGroup"
-            android:label="ActivityGroup" />
+             android:label="ActivityGroup"/>
 
         <activity android:name="android.widget.cts.MockURLSpanTestActivity"
-            android:label="MockURLSpanTestActivity"
-            android:launchMode="singleTask"
-            android:alwaysRetainTaskState="true"
-            android:configChanges="orientation|keyboardHidden">
+             android:label="MockURLSpanTestActivity"
+             android:launchMode="singleTask"
+             android:alwaysRetainTaskState="true"
+             android:configChanges="orientation|keyboardHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-                <data android:scheme="ctstest" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+                <data android:scheme="ctstest"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.widget.cts.PointerIconCtsActivity">
+        <activity android:name="android.widget.cts.PointerIconCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.MagnifierCtsActivity"
-                  android:label="MagnifierCtsActivity">
+             android:label="MagnifierCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.inline.InlineContentViewCtsActivity"
-                  android:label="InlineContentViewCtsActivity">
+             android:label="InlineContentViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.widget.cts"
-                     android:label="CTS tests of android.widget">
+         android:targetPackage="android.widget.cts"
+         android:label="CTS tests of android.widget">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/widget/res/layout/edittext_singleline_maxlength.xml b/tests/tests/widget/res/layout/edittext_singleline_maxlength.xml
new file mode 100644
index 0000000..548d42d
--- /dev/null
+++ b/tests/tests/widget/res/layout/edittext_singleline_maxlength.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+  <TextView
+      android:id="@+id/textview_explicit_singleline_max_length"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/even_more_long_text"
+      android:singleLine="true" />
+  <EditText
+      android:id="@+id/edittext_explicit_singleline_max_length"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/even_more_long_text"
+      android:singleLine="true" />
+  <EditText
+      android:id="@+id/edittext_explicit_singleline_with_explicit_max_length"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/even_more_long_text"
+      android:maxLength="2000"
+      android:singleLine="true" />
+  <EditText
+      android:id="@+id/edittext_multiLine"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:inputType="textMultiLine"
+      android:text="@string/even_more_long_text" />
+  <EditText
+      android:id="@+id/edittext_singleLine"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:inputType="text"
+      android:text="@string/even_more_long_text" />
+
+</LinearLayout>
diff --git a/tests/tests/widget/res/values/strings.xml b/tests/tests/widget/res/values/strings.xml
index 9e36cc0..91084e9 100644
--- a/tests/tests/widget/res/values/strings.xml
+++ b/tests/tests/widget/res/values/strings.xml
@@ -176,6 +176,7 @@
 with no fading. I have made this string longer to fix this case. If you are correcting this
 text, I would love to see the kind of devices you guys now use! Guys, maybe some devices need longer string!
 I think so, so how about double this string, like copy and paste! </string>
+    <string name="even_more_long_text">This is even more long string which exceeds the character limit of the single line edit text. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, feugiat in, orci. In hac habitasse platea dictumst. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, feugiat in, orci. In hac habitasse platea dictumst. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, feugiat in, orci. In hac habitasse platea dictumst. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, feugiat in, orci. In hac habitasse platea dictumst.</string>
     <string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string>
 
     <string name="popup_show">Show popup</string>
diff --git a/tests/tests/widget/src/android/widget/cts/EditTextTest.java b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
index 957edf0..a9785a7 100755
--- a/tests/tests/widget/src/android/widget/cts/EditTextTest.java
+++ b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
@@ -28,7 +28,9 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.text.Editable;
+import android.text.InputFilter;
 import android.text.Layout;
+import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.method.ArrowKeyMovementMethod;
 import android.text.method.MovementMethod;
@@ -39,6 +41,7 @@
 import android.util.Xml;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.EditorInfo;
 import android.widget.EditText;
 import android.widget.TextView;
 import android.widget.TextView.BufferType;
@@ -527,4 +530,165 @@
         CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mEditText1, KeyEvent.KEYCODE_NUMPAD_ENTER);
         assertTrue(mEditText2.hasFocus());
     }
+
+    private static final int FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_explicit_singleLine() {
+        mActivity.setContentView(R.layout.edittext_singleline_maxlength);
+
+        EditText et = (EditText) mActivity.findViewById(
+                R.id.edittext_explicit_singleline_max_length);
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_explicit_singleLine_with_explicit_maxLength() {
+        mActivity.setContentView(R.layout.edittext_singleline_maxlength);
+
+        EditText et = (EditText) mActivity.findViewById(
+                R.id.edittext_explicit_singleline_with_explicit_max_length);
+        // This EditText has maxLength=2000 and singeLine=true.
+        // User specified maxLength must be respected.
+        assertTrue(et.getText().length() <= 2000);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_singleLine_from_inputType() {
+        mActivity.setContentView(R.layout.edittext_singleline_maxlength);
+
+        EditText et = (EditText) mActivity.findViewById(R.id.edittext_singleLine);
+        // This EditText has inputType="text" which is translated to singleLine.
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_multiline() {
+        mActivity.setContentView(R.layout.edittext_singleline_maxlength);
+
+        EditText et = (EditText) mActivity.findViewById(R.id.edittext_multiLine);
+        // Multiline text doesn't have automated char limit.
+        assertTrue(et.getText().length() > FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_textView() {
+        mActivity.setContentView(R.layout.edittext_singleline_maxlength);
+
+        TextView tv = (TextView) mActivity.findViewById(
+                R.id.textview_explicit_singleline_max_length);
+        // Automated maxLength for singline text is not applied to TextView.
+        assertTrue(tv.getText().length() > FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_SetSingleLine() {
+        EditText et = new EditText(mActivity);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+        et.setSingleLine();
+
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_setInputType_singleLine() {
+        EditText et = new EditText(mActivity);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+        et.setInputType(EditorInfo.TYPE_CLASS_TEXT);
+
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_setInputType_multiLine() {
+        EditText et = new EditText(mActivity);
+        et.setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+
+        assertTrue(et.getText().length() > FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    class DummyFilter implements InputFilter {
+        @Override
+        public CharSequence filter(
+                CharSequence source,
+                int start,
+                int end,
+                Spanned dest,
+                int dstart,
+                int dend) {
+            return source;
+        }
+    }
+
+    private final InputFilter mFilterA = new DummyFilter();
+    private final InputFilter mFilterB = new DummyFilter();
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_SetSingleLine_preserveFilters() {
+        EditText et = new EditText(mActivity);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+        et.setFilters(new InputFilter[] { mFilterA, mFilterB });
+        et.setSingleLine();
+
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+
+        assertEquals(3, et.getFilters().length);
+        assertEquals(et.getFilters()[0], mFilterA);
+        assertEquals(et.getFilters()[1], mFilterB);
+        assertTrue(et.getFilters()[2] instanceof InputFilter.LengthFilter);
+
+        et.setSingleLine(false);
+        assertEquals(2, et.getFilters().length);
+        assertEquals(et.getFilters()[0], mFilterA);
+        assertEquals(et.getFilters()[1], mFilterB);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_SetSingleLine_preserveFilters_mixtureFilters() {
+        EditText et = new EditText(mActivity);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+        et.setSingleLine();
+        et.setFilters(new InputFilter[] { mFilterA, et.getFilters()[0], mFilterB });
+
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+
+        et.setSingleLine(false);
+        assertEquals(2, et.getFilters().length);
+        assertEquals(et.getFilters()[0], mFilterA);
+        assertEquals(et.getFilters()[1], mFilterB);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_SetSingleLine_preserveFilters_anotherLengthFilter() {
+        EditText et = new EditText(mActivity);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+        final InputFilter myFilter =
+                new InputFilter.LengthFilter(FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+        et.setFilters(new InputFilter[] { myFilter });
+        et.setSingleLine();
+
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+
+        // setSingleLine(true) must not add new filter since there is already LengthFilter.
+        assertEquals(1, et.getFilters().length);
+        assertEquals(et.getFilters()[0], myFilter);
+
+        // setSingleLine(false) must not remove my custom filter.
+        et.setSingleLine(false);
+        assertEquals(1, et.getFilters().length);
+        assertEquals(et.getFilters()[0], myFilter);
+    }
+
 }
diff --git a/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java b/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
index 7c656ba..71845f2 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
@@ -19,6 +19,7 @@
 import static com.android.compatibility.common.util.CtsMockitoUtils.within;
 
 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.mockito.Mockito.any;
@@ -36,9 +37,11 @@
 import android.graphics.drawable.ColorDrawable;
 import android.os.SystemClock;
 import android.view.Gravity;
+import android.view.InputDevice;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.MotionEvent;
 import android.view.SubMenu;
 import android.view.View;
 import android.widget.EditText;
@@ -430,6 +433,56 @@
         }
     }
 
+    @Test
+    public void testHoverSelectsMenuItem() throws Throwable {
+        mBuilder = new Builder().withExtraItems(100).withAnchorId(R.id.anchor_upper_left);
+        mActivityRule.runOnUiThread(mBuilder::show);
+
+        mInstrumentation.waitForIdleSync();
+        ListView menuItemList = mPopupMenu.getMenuListView();
+
+        assertEquals(0, menuItemList.getFirstVisiblePosition());
+        emulateHoverOverVisibleItems(mInstrumentation, menuItemList);
+
+        // Select the last item to force menu scrolling and emulate hover again.
+        mActivityRule.runOnUiThread(
+                () -> menuItemList.setSelectionFromTop(mPopupMenu.getMenu().size() - 1, 0));
+        mInstrumentation.waitForIdleSync();
+
+        assertNotEquals("Too few menu items to test for scrolling",
+                0, menuItemList.getFirstVisiblePosition());
+        emulateHoverOverVisibleItems(mInstrumentation, menuItemList);
+
+        mPopupMenu = null;
+    }
+
+    private void emulateHoverOverVisibleItems(Instrumentation instrumentation, ListView listView) {
+        final int childCount = listView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View itemView = listView.getChildAt(i);
+            injectMouseEvent(instrumentation, itemView, MotionEvent.ACTION_HOVER_MOVE);
+
+            // Wait for the system to process all events in the queue.
+            instrumentation.waitForIdleSync();
+
+            // Hovered menu item should be selected.
+            assertEquals(listView.getFirstVisiblePosition() + i,
+                    listView.getSelectedItemPosition());
+        }
+    }
+
+    private static void injectMouseEvent(Instrumentation instrumentation, View view, int action) {
+        final int[] xy = new int[2];
+        view.getLocationOnScreen(xy);
+        final int x = xy[0] + view.getWidth() / 2;
+        final int y = xy[1] + view.getHeight() / 2;
+        long eventTime = SystemClock.uptimeMillis();
+        MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action, x, y, 0);
+        event.setSource(InputDevice.SOURCE_MOUSE);
+        instrumentation.sendPointerSync(event);
+        event.recycle();
+    }
+
     /**
      * Inner helper class to configure an instance of {@link PopupMenu} for the specific test.
      * The main reason for its existence is that once a popup menu is shown with the show() method,
@@ -441,6 +494,7 @@
         private boolean mHasDismissListener;
         private boolean mHasMenuItemClickListener;
         private boolean mInflateWithInflater;
+        private int mExtraItemCount;
 
         private int mAnchorId = R.id.anchor_middle_left;
         private int mPopupMenuContent = R.menu.popup_menu;
@@ -507,6 +561,11 @@
             return this;
         }
 
+        public Builder withExtraItems(int count) {
+            mExtraItemCount = count;
+            return this;
+        }
+
         public void configure() {
             mAnchor = mActivity.findViewById(mAnchorId);
             if (!mUseCustomGravity && !mUseCustomPopupResource) {
@@ -543,6 +602,11 @@
             }
 
             mPopupMenu.setForceShowIcon(mForceShowIcon);
+
+            // Add extra items.
+            for (int i = 0; i < mExtraItemCount; i++) {
+                mPopupMenu.getMenu().add("Extra item " + i);
+            }
         }
 
         public void show() {
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentCallbackTest.java b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentCallbackTest.java
new file mode 100644
index 0000000..126b4da
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentCallbackTest.java
@@ -0,0 +1,990 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.cts;
+
+import static android.view.OnReceiveContentCallback.Payload.FLAG_CONVERT_TO_PLAIN_TEXT;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_AUTOFILL;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_CLIPBOARD;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_DRAG_AND_DROP;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_INPUT_METHOD;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_PROCESS_TEXT;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import static java.util.Collections.singleton;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ClipboardManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.method.QwertyKeyListener;
+import android.text.method.TextKeyListener.Capitalize;
+import android.text.style.UnderlineSpan;
+import android.view.DragEvent;
+import android.view.OnReceiveContentCallback;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.autofill.AutofillValue;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputContentInfo;
+import android.widget.TextView;
+import android.widget.TextView.BufferType;
+import android.widget.TextViewOnReceiveContentCallback;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mockito;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Test {@link OnReceiveContentCallback} and its integration with {@link TextView}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class TextViewOnReceiveContentCallbackTest {
+    public static final Uri SAMPLE_CONTENT_URI = Uri.parse("content://com.example/path");
+    @Rule
+    public ActivityTestRule<TextViewCtsActivity> mActivityRule =
+            new ActivityTestRule<>(TextViewCtsActivity.class);
+
+    private Activity mActivity;
+    private TextView mTextView;
+    private OnReceiveContentCallback<TextView> mDefaultReceiver;
+    private MockReceiverWrapper mMockReceiverWrapper;
+    private OnReceiveContentCallback<TextView> mMockReceiver;
+    private ClipboardManager mClipboardManager;
+
+    @Before
+    public void before() {
+        mActivity = mActivityRule.getActivity();
+        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        mTextView = mActivity.findViewById(R.id.textview_text);
+        mDefaultReceiver = new TextViewOnReceiveContentCallback();
+
+        mMockReceiverWrapper = new MockReceiverWrapper();
+        mMockReceiver = mMockReceiverWrapper.getMock();
+
+        mClipboardManager = mActivity.getSystemService(ClipboardManager.class);
+        mClipboardManager.clearPrimaryClip();
+
+        configureAppTargetSdkToS();
+    }
+
+    @After
+    public void after() {
+        resetTargetSdk();
+    }
+
+    // ============================================================================================
+    // Tests to verify TextView APIs/accessors/defaults related to OnReceiveContentCallback.
+    // ============================================================================================
+
+    @UiThreadTest
+    @Test
+    public void testTextView_getAndSetOnReceiveContentCallback() throws Exception {
+        // Verify that by default the getter returns null.
+        assertThat(mTextView.getOnReceiveContentCallback()).isNull();
+
+        // Verify that after setting a custom receiver, the getter returns it.
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+        assertThat(mTextView.getOnReceiveContentCallback()).isSameInstanceAs(mMockReceiverWrapper);
+
+        // Verify that setting a null receiver clears the previously set custom receiver.
+        mTextView.setOnReceiveContentCallback(null);
+        assertThat(mTextView.getOnReceiveContentCallback()).isNull();
+    }
+
+    @UiThreadTest
+    @Test
+    public void testTextView_onCreateInputConnection_nullEditorInfo() throws Exception {
+        initTextViewForEditing("xz", 1);
+        try {
+            mTextView.onCreateInputConnection(null);
+            Assert.fail("Expected exception");
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    public void testTextView_onCreateInputConnection_noCustomReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Call onCreateInputConnection() and assert that contentMimeTypes is not set when there is
+        // no custom receiver configured.
+        EditorInfo editorInfo = new EditorInfo();
+        InputConnection ic = mTextView.onCreateInputConnection(editorInfo);
+        assertThat(ic).isNotNull();
+        assertThat(editorInfo.contentMimeTypes).isNull();
+    }
+
+    @UiThreadTest
+    @Test
+    public void testTextView_onCreateInputConnection_customReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        Set<String> receiverMimeTypes = Set.of("text/plain", "image/png", "video/mp4");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Call onCreateInputConnection() and assert that contentMimeTypes is set from the receiver.
+        EditorInfo editorInfo = new EditorInfo();
+        InputConnection ic = mTextView.onCreateInputConnection(editorInfo);
+        assertThat(ic).isNotNull();
+        assertThat(editorInfo.contentMimeTypes).isEqualTo(receiverMimeTypes.toArray(new String[0]));
+        verify(mMockReceiver, times(1)).getSupportedMimeTypes(eq(mTextView));
+        verifyNoMoreInteractions(mMockReceiver);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testTextView_onCreateInputConnection_customReceiver_oldTargetSdk()
+            throws Exception {
+        configureAppTargetSdkToR();
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        Set<String> receiverMimeTypes = Set.of("text/plain", "image/png", "video/mp4");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Call onCreateInputConnection() and assert that contentMimeTypes is set from the receiver.
+        EditorInfo editorInfo = new EditorInfo();
+        InputConnection ic = mTextView.onCreateInputConnection(editorInfo);
+        assertThat(ic).isNotNull();
+        assertThat(editorInfo.contentMimeTypes).isEqualTo(receiverMimeTypes.toArray(new String[0]));
+        verify(mMockReceiver, times(1)).getSupportedMimeTypes(eq(mTextView));
+        verifyNoMoreInteractions(mMockReceiver);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testTextView_getAutofillType_noCustomReceiver() throws Exception {
+        initTextViewForEditing("", 0);
+        assertThat(mTextView.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testTextView_getAutofillType_customReceiver() throws Exception {
+        initTextViewForEditing("", 0);
+
+        // Setup: Configure the receiver to a mock impl that supports text and images.
+        Set<String> receiverMimeTypes = Set.of("text/*", "image/*");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Assert that the autofill type returned is still AUTOFILL_TYPE_TEXT.
+        assertThat(mTextView.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
+        verifyZeroInteractions(mMockReceiver);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testTextView_getAutofillType_customReceiver_oldTargetSdk() throws Exception {
+        configureAppTargetSdkToR();
+        initTextViewForEditing("", 0);
+
+        // Setup: Configure the receiver to a mock impl that supports text and images.
+        Set<String> receiverMimeTypes = Set.of("text/*", "image/*");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Assert that the autofill type returned is still AUTOFILL_TYPE_TEXT.
+        assertThat(mTextView.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
+        verifyZeroInteractions(mMockReceiver);
+    }
+
+    // ============================================================================================
+    // Tests to verify the behavior of TextViewOnReceiveContentCallback.
+    // ============================================================================================
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_getSupportedMimeTypes() throws Exception {
+        assertThat(mDefaultReceiver.getSupportedMimeTypes(mTextView))
+                .isEqualTo(singleton("text/*"));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_text() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = ClipData.newPlainText("test", "y");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("xyz", 2);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_styledText() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        UnderlineSpan underlineSpan = new UnderlineSpan();
+        SpannableStringBuilder ssb = new SpannableStringBuilder("hi world");
+        ssb.setSpan(underlineSpan, 3, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        ClipData clip = ClipData.newPlainText("test", ssb);
+
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("xhi worldz", 9);
+        int spanStart = mTextView.getEditableText().getSpanStart(underlineSpan);
+        assertThat(spanStart).isEqualTo(4);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_text_convertToPlainText() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = ClipData.newPlainText("test", "y");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, FLAG_CONVERT_TO_PLAIN_TEXT);
+
+        assertTextAndCursorPosition("xyz", 2);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_styledText_convertToPlainText() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        UnderlineSpan underlineSpan = new UnderlineSpan();
+        SpannableStringBuilder ssb = new SpannableStringBuilder("hi world");
+        ssb.setSpan(underlineSpan, 3, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        ClipData clip = ClipData.newPlainText("test", ssb);
+
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, FLAG_CONVERT_TO_PLAIN_TEXT);
+
+        assertTextAndCursorPosition("xhi worldz", 9);
+        int spanStart = mTextView.getEditableText().getSpanStart(underlineSpan);
+        assertThat(spanStart).isEqualTo(-1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_html() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = ClipData.newHtmlText("test", "*y*", "<b>y</b>");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("xyz", 2);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_html_convertToPlainText() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = ClipData.newHtmlText("test", "*y*", "<b>y</b>");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, FLAG_CONVERT_TO_PLAIN_TEXT);
+
+        assertTextAndCursorPosition("x*y*z", 4);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_unsupportedMimeType() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = new ClipData("test", new String[]{"video/mp4"},
+                new ClipData.Item("text", "html", null, SAMPLE_CONTENT_URI));
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("xhtmlz", 5);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_unsupportedMimeType_convertToPlainText()
+            throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = new ClipData("test", new String[]{"video/mp4"},
+                new ClipData.Item("text", "html", null, SAMPLE_CONTENT_URI));
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD,
+                FLAG_CONVERT_TO_PLAIN_TEXT);
+
+        assertTextAndCursorPosition("xtextz", 5);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_multipleItemsInClipData() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = ClipData.newPlainText("test", "ONE");
+        clip.addItem(new ClipData.Item("TWO"));
+        clip.addItem(new ClipData.Item("THREE"));
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("xONE\nTWO\nTHREEz", 14);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_noSelectionPriorToPaste() throws Exception {
+        // Set the text and then clear the selection (ie, ensure that nothing is selected and
+        // that the cursor is not present).
+        initTextViewForEditing("xz", 0);
+        Selection.removeSelection(mTextView.getEditableText());
+        assertTextAndCursorPosition("xz", -1);
+
+        // Pasting should still work (should just insert the text at the beginning).
+        ClipData clip = ClipData.newPlainText("test", "y");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("yxz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_selectionStartAndEndSwapped() throws Exception {
+        initTextViewForEditing("", 0);
+
+        // Set the selection such that "end" is before "start".
+        mTextView.setText("hey", BufferType.EDITABLE);
+        Selection.setSelection(mTextView.getEditableText(), 3, 1);
+        assertTextAndSelection("hey", 3, 1);
+
+        // Pasting should still work (should still successfully overwrite the selection).
+        ClipData clip = ClipData.newPlainText("test", "i");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("hi", 2);
+    }
+
+    // ============================================================================================
+    // Tests to verify that the OnReceiveContentCallback is invoked for all the appropriate user
+    // interactions:
+    // * Paste from clipboard ("Paste" and "Paste as plain text" actions)
+    // * Content insertion from IME
+    // * Drag and drop
+    // * Autofill
+    // * Process text (Intent.ACTION_PROCESS_TEXT)
+    // ============================================================================================
+
+    @UiThreadTest
+    @Test
+    public void testPaste_noCustomReceiver() throws Exception {
+        // Setup: Populate the text field.
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Copy text to the clipboard.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        copyToClipboard(clip);
+
+        // Trigger the "Paste" action. This should execute the default receiver.
+        boolean result = triggerContextMenuAction(android.R.id.paste);
+        assertThat(result).isTrue();
+        assertTextAndCursorPosition("xyz", 2);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testPaste_customReceiver() throws Exception {
+        // Setup: Populate the text field.
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Copy text to the clipboard.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        copyToClipboard(clip);
+
+        // Setup: Configure the receiver to a mock impl.
+        Set<String> receiverMimeTypes = Set.of("text/plain");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger the "Paste" action and assert that the custom receiver was executed.
+        triggerContextMenuAction(android.R.id.paste);
+        verify(mMockReceiver, times(1)).getSupportedMimeTypes(eq(mTextView));
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), richContentDataEq(clip, SOURCE_CLIPBOARD, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testPaste_customReceiver_unsupportedMimeType() throws Exception {
+        // Setup: Populate the text field.
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Copy a URI to the clipboard with a MIME type that's not supported by the receiver.
+        ClipData clip = new ClipData("test", new String[]{"video/mp4"},
+                new ClipData.Item("y", null, SAMPLE_CONTENT_URI));
+        copyToClipboard(clip);
+
+        // Setup: Configure the receiver to a mock impl.
+        Set<String> receiverMimeTypes = Set.of("text/plain", "video/avi");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger the "Paste" action and assert that the custom receiver was not executed.
+        triggerContextMenuAction(android.R.id.paste);
+        verify(mMockReceiver, times(1)).getSupportedMimeTypes(eq(mTextView));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xyz", 2);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testPasteAsPlainText_noCustomReceiver() throws Exception {
+        // Setup: Populate the text field.
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Copy HTML to the clipboard.
+        ClipData clip = ClipData.newHtmlText("test", "*y*", "<b>y</b>");
+        copyToClipboard(clip);
+
+        // Trigger the "Paste as plain text" action. This should execute the platform paste
+        // handling, so the content should be inserted according to whatever behavior is implemented
+        // in the OS version that's running.
+        boolean result = triggerContextMenuAction(android.R.id.pasteAsPlainText);
+        assertThat(result).isTrue();
+        assertTextAndCursorPosition("x*y*z", 4);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testPasteAsPlainText_customReceiver() throws Exception {
+        // Setup: Populate the text field.
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Copy text to the clipboard.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        copyToClipboard(clip);
+
+        // Setup: Configure the receiver to a mock impl.
+        Set<String> receiverMimeTypes = Set.of("text/plain");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger the "Paste as plain text" action and assert that the custom receiver was
+        // executed.
+        triggerContextMenuAction(android.R.id.pasteAsPlainText);
+        verify(mMockReceiver, times(1)).getSupportedMimeTypes(eq(mTextView));
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView),
+                richContentDataEq(clip, SOURCE_CLIPBOARD, FLAG_CONVERT_TO_PLAIN_TEXT));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_noCustomReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Trigger the IME's commitContent() call and assert its outcome.
+        boolean result = triggerImeCommitContent("image/png");
+        assertThat(result).isFalse();
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_customReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        Set<String> receiverMimeTypes = Set.of("text/*", "image/*");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger the IME's commitContent() call and assert that the custom receiver was executed.
+        // Note: We expect 2 calls to getSupportedMimeTypes() -- one from onCreateInputConnection()
+        // to populate EditorInfo.contentMimeTypes and one from commitContent().
+        triggerImeCommitContent("image/png");
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(2)).getSupportedMimeTypes(eq(mTextView));
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), richContentDataEq(clip, SOURCE_INPUT_METHOD, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_customReceiver_unsupportedMimeType() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        Set<String> receiverMimeTypes = Set.of("text/*", "image/*");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger the IME's commitContent() call and assert that the custom receiver was not
+        // executed.
+        triggerImeCommitContent("video/mp4");
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(2)).getSupportedMimeTypes(eq(mTextView));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_customReceiver_oldTargetSdk() throws Exception {
+        configureAppTargetSdkToR();
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        Set<String> receiverMimeTypes = Set.of("text/*", "image/*");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger the IME's commitContent() call and assert that the custom receiver was executed.
+        triggerImeCommitContent("image/png");
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(2)).getSupportedMimeTypes(eq(mTextView));
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), richContentDataEq(clip, SOURCE_INPUT_METHOD, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_linkUri() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        Set<String> receiverMimeTypes = Set.of("text/*", "image/*");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger the IME's commitContent() call with a linkUri and assert receiver extras.
+        Uri sampleLinkUri = Uri.parse("http://example.com");
+        triggerImeCommitContent("image/png", sampleLinkUri, null);
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView),
+                richContentDataEq(clip, SOURCE_INPUT_METHOD, 0, sampleLinkUri, null));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_opts() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        Set<String> receiverMimeTypes = Set.of("text/*", "image/*");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger the IME's commitContent() call with opts and assert receiver extras.
+        String sampleOptValue = "sampleOptValue";
+        triggerImeCommitContent("image/png", null, sampleOptValue);
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView),
+                richContentDataEq(clip, SOURCE_INPUT_METHOD, 0, null, sampleOptValue));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_linkUriAndOpts() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        Set<String> receiverMimeTypes = Set.of("text/*", "image/*");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger the IME's commitContent() call with a linkUri & opts and assert receiver extras.
+        Uri sampleLinkUri = Uri.parse("http://example.com");
+        String sampleOptValue = "sampleOptValue";
+        triggerImeCommitContent("image/png", sampleLinkUri, sampleOptValue);
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView),
+                richContentDataEq(clip, SOURCE_INPUT_METHOD, 0, sampleLinkUri, sampleOptValue));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDragAndDrop_noCustomReceiver() throws Exception {
+        initTextViewForEditing("xz", 2);
+
+        // Trigger drop event. This should execute the default receiver.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        triggerDropEvent(clip);
+        assertTextAndCursorPosition("yxz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDragAndDrop_customReceiver() throws Exception {
+        initTextViewForEditing("xz", 2);
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(singleton("text/*"));
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger drop event and assert that the custom receiver was executed.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        triggerDropEvent(clip);
+        verify(mMockReceiver, times(1)).getSupportedMimeTypes(eq(mTextView));
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), richContentDataEq(clip, SOURCE_DRAG_AND_DROP, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        // Note: The cursor is moved to the location of the drop before calling the receiver.
+        assertTextAndCursorPosition("xz", 0);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDragAndDrop_customReceiver_unsupportedMimeType() throws Exception {
+        initTextViewForEditing("xz", 2);
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(singleton("text/*"));
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger drop event and assert that the custom receiver was not executed.
+        ClipData clip = new ClipData("test", new String[]{"video/mp4"},
+                new ClipData.Item("y", null, SAMPLE_CONTENT_URI));
+        triggerDropEvent(clip);
+        verify(mMockReceiver, times(1)).getSupportedMimeTypes(eq(mTextView));
+        verifyNoMoreInteractions(mMockReceiver);
+        // Note: The cursor is moved to the location of the drop before calling the receiver.
+        assertTextAndCursorPosition("yxz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAutofill_noCustomReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Trigger autofill. This should execute the default receiver.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        triggerAutofill(clip);
+        assertTextAndCursorPosition("y", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAutofill_customReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(singleton("text/*"));
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger autofill and assert that the custom receiver was executed.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        triggerAutofill(clip);
+        verify(mMockReceiver, times(1)).getSupportedMimeTypes(eq(mTextView));
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), richContentDataEq(clip, SOURCE_AUTOFILL, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAutofill_customReceiver_unsupportedMimeType() throws Exception {
+        initTextViewForEditing("xz", 1);
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(singleton("text/*"));
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        // Trigger autofill and assert that the custom receiver was not executed.
+        // Note: We expect 2 calls to getSupportedMimeTypes(). The first call is to check whether
+        // the custom callback supports the MIME type of the clip. Since it doesn't, the default
+        // callback is executed; this calls setText() which triggers onCreateInputConnection()
+        // which reads the supported MIME types of the custom callback.
+        ClipData clip = new ClipData("test", new String[]{"video/mp4"},
+                new ClipData.Item("y", null, SAMPLE_CONTENT_URI));
+        triggerAutofill(clip);
+        verify(mMockReceiver, times(2)).getSupportedMimeTypes(eq(mTextView));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("y", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAutofill_oldTargetSdk() throws Exception {
+        configureAppTargetSdkToR();
+        initTextViewForEditing("xz", 1);
+
+        // Try autofill with text. This should fill the field.
+        CharSequence text = "abc";
+        triggerAutofill(text);
+        assertTextAndCursorPosition("abc", 3);
+
+        // Try autofill with a ClipData. This should fill the field.
+        ClipData clip = ClipData.newPlainText("test", "xyz");
+        triggerAutofill(clip);
+        assertTextAndCursorPosition("xyz", 3);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testProcessText_noCustomReceiver() throws Exception {
+        initTextViewForEditing("Original text", 0);
+        Selection.setSelection(mTextView.getEditableText(), 0, mTextView.getText().length());
+
+        String newText = "Replacement text";
+        triggerProcessTextOnActivityResult(newText);
+        assertTextAndCursorPosition(newText, newText.length());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testProcessText_customReceiver() throws Exception {
+        String originalText = "Original text";
+        initTextViewForEditing(originalText, 0);
+        Selection.setSelection(mTextView.getEditableText(), 0, originalText.length());
+        assertTextAndSelection(originalText, 0, originalText.length());
+
+        Set<String> receiverMimeTypes = Set.of("text/plain");
+        when(mMockReceiver.getSupportedMimeTypes(mTextView)).thenReturn(receiverMimeTypes);
+        mTextView.setOnReceiveContentCallback(mMockReceiverWrapper);
+
+        String newText = "Replacement text";
+        triggerProcessTextOnActivityResult(newText);
+        ClipData clip = ClipData.newPlainText("", newText);
+        verify(mMockReceiver, times(1)).getSupportedMimeTypes(eq(mTextView));
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), richContentDataEq(clip, SOURCE_PROCESS_TEXT, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndSelection(originalText, 0, originalText.length());
+    }
+
+
+    private void initTextViewForEditing(final String text, final int cursorPosition) {
+        mTextView.setKeyListener(QwertyKeyListener.getInstance(false, Capitalize.NONE));
+        mTextView.setTextIsSelectable(true);
+        mTextView.requestFocus();
+
+        SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+        mTextView.setText(ssb, BufferType.EDITABLE);
+        mTextView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        Selection.setSelection(mTextView.getEditableText(), cursorPosition);
+
+        assertWithMessage("TextView should have focus").that(mTextView.hasFocus()).isTrue();
+        assertTextAndCursorPosition(text, cursorPosition);
+    }
+
+    private void assertTextAndCursorPosition(String expectedText, int cursorPosition) {
+        assertTextAndSelection(expectedText, cursorPosition, cursorPosition);
+    }
+
+    private void assertTextAndSelection(String expectedText, int start, int end) {
+        assertThat(mTextView.getText().toString()).isEqualTo(expectedText);
+        int[] expected = new int[]{start, end};
+        int[] actual = new int[]{mTextView.getSelectionStart(), mTextView.getSelectionEnd()};
+        assertWithMessage("Unexpected selection start/end indexes")
+                .that(actual).isEqualTo(expected);
+    }
+
+    private void onReceive(final OnReceiveContentCallback<TextView> receiver,
+            final ClipData clip, final int source, final int flags) {
+        OnReceiveContentCallback.Payload payload =
+                new OnReceiveContentCallback.Payload.Builder(clip, source)
+                .setFlags(flags)
+                .build();
+        receiver.onReceiveContent(mTextView, payload);
+    }
+
+    private void resetTargetSdk() {
+        mActivity.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+    }
+
+    private void configureAppTargetSdkToR() {
+        mActivity.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.R;
+    }
+
+    private void configureAppTargetSdkToS() {
+        mActivity.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.S;
+    }
+
+    // This wrapper is used so that we only mock and verify the public callback methods. In addition
+    // to the public methods, the OnReceiveContentCallback interface has some hidden default
+    // methods; we don't want to mock or assert calls to these helper functions (they are an
+    // implementation detail).
+    private static class MockReceiverWrapper implements OnReceiveContentCallback<TextView> {
+        private final OnReceiveContentCallback<TextView> mMock;
+
+        @SuppressWarnings("unchecked")
+        MockReceiverWrapper() {
+            this.mMock = Mockito.mock(OnReceiveContentCallback.class);
+        }
+
+        public OnReceiveContentCallback<TextView> getMock() {
+            return mMock;
+        }
+
+        @Override
+        public boolean onReceiveContent(TextView view, OnReceiveContentCallback.Payload payload) {
+            return mMock.onReceiveContent(view, payload);
+        }
+
+        @NonNull
+        @Override
+        public Set<String> getSupportedMimeTypes(TextView view) {
+            return mMock.getSupportedMimeTypes(view);
+        }
+    }
+
+    private void copyToClipboard(ClipData clip) {
+        mClipboardManager.setPrimaryClip(clip);
+    }
+
+    private boolean triggerContextMenuAction(final int actionId) {
+        return mTextView.onTextContextMenuItem(actionId);
+    }
+
+    private boolean triggerImeCommitContent(String mimeType) {
+        return triggerImeCommitContent(mimeType, null, null);
+    }
+
+    private boolean triggerImeCommitContent(String mimeType, Uri linkUri, String extra) {
+        final InputContentInfo contentInfo = new InputContentInfo(
+                SAMPLE_CONTENT_URI,
+                new ClipDescription("from test", new String[]{mimeType}),
+                linkUri);
+        final Bundle opts;
+        if (extra == null) {
+            opts = null;
+        } else {
+            opts = new Bundle();
+            opts.putString(RichContentDataArgumentMatcher.EXTRA_KEY, extra);
+        }
+        EditorInfo editorInfo = new EditorInfo();
+        InputConnection ic = mTextView.onCreateInputConnection(editorInfo);
+        return ic.commitContent(contentInfo, 0, opts);
+    }
+
+    private void triggerAutofill(CharSequence text) {
+        mTextView.autofill(AutofillValue.forText(text));
+    }
+
+    private void triggerAutofill(ClipData clip) {
+        mTextView.autofill(AutofillValue.forRichContent(clip));
+    }
+
+    private boolean triggerDropEvent(ClipData clip) {
+        DragEvent dropEvent = createDragEvent(DragEvent.ACTION_DROP, mTextView.getX(),
+                mTextView.getY(), clip);
+        return mTextView.onDragEvent(dropEvent);
+    }
+
+    private static DragEvent createDragEvent(int action, float x, float y, ClipData clip) {
+        // DragEvent doesn't expose any API for instantiation, so we have to build it from a Parcel.
+        Parcel dest = Parcel.obtain();
+        dest.writeInt(action);
+        dest.writeFloat(x);
+        dest.writeFloat(y);
+        dest.writeInt(0); // Result
+        dest.writeInt(1); // ClipData
+        clip.writeToParcel(dest, 0);
+        dest.writeInt(1); // ClipDescription
+        clip.getDescription().writeToParcel(dest, 0);
+        dest.writeInt(0); // IDragAndDropPermissions
+        dest.setDataPosition(0);
+        return DragEvent.CREATOR.createFromParcel(dest);
+    }
+
+    private void triggerProcessTextOnActivityResult(CharSequence replacementText) {
+        Intent data = new Intent();
+        data.putExtra(Intent.EXTRA_PROCESS_TEXT, replacementText);
+        mTextView.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_OK, data);
+    }
+
+    private static OnReceiveContentCallback.Payload richContentDataEq(@NonNull ClipData clip,
+            int source, int flags) {
+        return argThat(new RichContentDataArgumentMatcher(clip, source, flags, null, null));
+    }
+
+    private static OnReceiveContentCallback.Payload richContentDataEq(@NonNull ClipData clip,
+            int source, int flags, Uri linkUri, String extra) {
+        return argThat(new RichContentDataArgumentMatcher(clip, source, flags, linkUri, extra));
+    }
+
+    private static class RichContentDataArgumentMatcher implements
+            ArgumentMatcher<OnReceiveContentCallback.Payload> {
+        public static final String EXTRA_KEY = "testExtra";
+
+        @NonNull private final ClipData mClip;
+        private final int mSource;
+        private final int mFlags;
+        @Nullable private final Uri mLinkUri;
+        @Nullable private final String mExtra;
+
+        private RichContentDataArgumentMatcher(@NonNull ClipData clip, int source, int flags,
+                @Nullable Uri linkUri, @Nullable String extra) {
+            mClip = clip;
+            mSource = source;
+            mFlags = flags;
+            mLinkUri = linkUri;
+            mExtra = extra;
+        }
+
+        @Override
+        public boolean matches(OnReceiveContentCallback.Payload actual) {
+            ClipData.Item expectedItem = mClip.getItemAt(0);
+            ClipData.Item actualItem = actual.getClip().getItemAt(0);
+            return Objects.equals(expectedItem.getText(), actualItem.getText())
+                    && Objects.equals(expectedItem.getUri(), actualItem.getUri())
+                    && mSource == actual.getSource()
+                    && mFlags == actual.getFlags()
+                    && Objects.equals(mLinkUri, actual.getLinkUri())
+                    && extrasMatch(actual.getExtras());
+        }
+
+        private boolean extrasMatch(Bundle actualExtras) {
+            if (mExtra == null) {
+                return actualExtras == null;
+            }
+            String actualExtraValue = actualExtras.getString(EXTRA_KEY);
+            return Objects.equals(mExtra, actualExtraValue);
+        }
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index e0fb5e8..6a9bdea 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -323,6 +323,90 @@
     }
 
     @Test
+    public void testForceBoldText_textIsBolded() throws Throwable {
+        mActivityRule.runOnUiThread(() -> mTextView = findTextView(R.id.textview_text));
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(400, mTextView.getTypeface().getWeight());
+
+        Configuration cf = new Configuration();
+        cf.forceBoldText = Configuration.FORCE_BOLD_TEXT_YES;
+        mActivityRule.runOnUiThread(() -> mTextView.dispatchConfigurationChanged(cf));
+        mInstrumentation.waitForIdleSync();
+
+        Typeface forceBoldedPaintTf = mTextView.getPaint().getTypeface();
+        assertEquals(700, forceBoldedPaintTf.getWeight());
+        assertEquals(400, mTextView.getTypeface().getWeight());
+    }
+
+    @Test
+    public void testForceBoldText_textIsUnbolded() throws Throwable {
+        Configuration cf = new Configuration();
+        cf.forceBoldText = Configuration.FORCE_BOLD_TEXT_YES;
+        mActivityRule.runOnUiThread(() -> {
+            mTextView = findTextView(R.id.textview_text);
+            mTextView.dispatchConfigurationChanged(cf);
+            cf.forceBoldText = Configuration.FORCE_BOLD_TEXT_NO;
+            mTextView.dispatchConfigurationChanged(cf);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        Typeface forceUnboldedPaintTf = mTextView.getPaint().getTypeface();
+        assertEquals(400, forceUnboldedPaintTf.getWeight());
+        assertEquals(400, mTextView.getTypeface().getWeight());
+    }
+
+    @Test
+    public void testForceBoldText_originalTypefaceKeptWhenEnabled() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mTextView = findTextView(R.id.textview_text);
+            Configuration cf = new Configuration();
+            cf.forceBoldText = Configuration.FORCE_BOLD_TEXT_YES;
+            mTextView.dispatchConfigurationChanged(cf);
+            mTextView.setTypeface(Typeface.MONOSPACE);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(Typeface.MONOSPACE, mTextView.getTypeface());
+
+        Typeface forceBoldedPaintTf = mTextView.getPaint().getTypeface();
+        assertTrue(forceBoldedPaintTf.isBold());
+        assertEquals(Typeface.create(Typeface.MONOSPACE, 700, false), forceBoldedPaintTf);
+    }
+
+
+    @Test
+    public void testForceBoldText_originalTypefaceIsKeptWhenDisabled() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mTextView = findTextView(R.id.textview_text);
+            Configuration cf = new Configuration();
+            cf.forceBoldText = Configuration.FORCE_BOLD_TEXT_NO;
+            mTextView.dispatchConfigurationChanged(cf);
+            mTextView.setTypeface(Typeface.MONOSPACE);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(Typeface.MONOSPACE, mTextView.getTypeface());
+        assertEquals(Typeface.MONOSPACE, mTextView.getPaint().getTypeface());
+    }
+
+    @Test
+    public void testForceBoldText_boldTypefaceIsBoldedWhenEnabled() throws Throwable {
+        Typeface originalTypeface = Typeface.create(Typeface.MONOSPACE, Typeface.BOLD);
+        mActivityRule.runOnUiThread(() -> {
+            mTextView = findTextView(R.id.textview_text);
+            Configuration cf = new Configuration();
+            cf.forceBoldText = Configuration.FORCE_BOLD_TEXT_YES;
+            mTextView.dispatchConfigurationChanged(cf);
+            mTextView.setTypeface(originalTypeface);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(originalTypeface, mTextView.getTypeface());
+        assertEquals(1000, mTextView.getPaint().getTypeface().getWeight());
+    }
+
+    @Test
     public void testAccessMovementMethod() throws Throwable {
         final CharSequence LONG_TEXT = "Scrolls the specified widget to the specified "
                 + "coordinates, except constrains the X scrolling position to the horizontal "
diff --git a/tests/tests/widget29/AndroidManifest.xml b/tests/tests/widget29/AndroidManifest.xml
index e8973fc..d338368 100644
--- a/tests/tests/widget29/AndroidManifest.xml
+++ b/tests/tests/widget29/AndroidManifest.xml
@@ -16,31 +16,31 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.widget.cts29">
+     package="android.widget.cts29">
 
     <application android:label="Android TestCase 29"
-            android:maxRecents="1"
-            android:multiArch="true"
-            android:supportsRtl="true"
-            android:theme="@android:style/Theme.Material.Light.DarkActionBar">
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:supportsRtl="true"
+         android:theme="@android:style/Theme.Material.Light.DarkActionBar">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.widget.cts29.CtsActivity"
-                  android:label="CtsActivity">
+             android:label="CtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.widget.cts29"
-                     android:label="(SDK 29) CTS tests of android.widget">
+         android:targetPackage="android.widget.cts29"
+         android:label="(SDK 29) CTS tests of android.widget">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/wifi/Android.bp b/tests/tests/wifi/Android.bp
index ed8daba..016541c 100644
--- a/tests/tests/wifi/Android.bp
+++ b/tests/tests/wifi/Android.bp
@@ -26,6 +26,7 @@
     srcs: [ "src/**/*.java" ],
 
     static_libs: [
+        "androidx.appcompat_appcompat",
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/ScheduleJobActivity.java b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/ScheduleJobActivity.java
index b447878..c1c292b 100644
--- a/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/ScheduleJobActivity.java
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/ScheduleJobActivity.java
@@ -57,7 +57,5 @@
         jobScheduler.schedule(jobInfo);
 
         Log.v(TAG,"Job scheduled: " + jobInfo);
-
-        finish();
     }
 }
diff --git a/tests/tests/wifi/TEST_MAPPING b/tests/tests/wifi/TEST_MAPPING
new file mode 100644
index 0000000..fde3a6a
--- /dev/null
+++ b/tests/tests/wifi/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsWifiTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.net.wifi.cts.VirtualDeviceNotSupported"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/tests/wifi/assets/BackupLegacyFormatSupplicantConf.txt b/tests/tests/wifi/assets/BackupLegacyFormatSupplicantConf.txt
index 1e296e6..2beffaf 100644
--- a/tests/tests/wifi/assets/BackupLegacyFormatSupplicantConf.txt
+++ b/tests/tests/wifi/assets/BackupLegacyFormatSupplicantConf.txt
@@ -1,8 +1,8 @@
 network={
         ssid="TestSsid1"
         key_mgmt=NONE
-        wep_key0="WepAscii1"
-        wep_key1="WepAscii2"
+        wep_key0="WepAscii12345"
+        wep_key1="WepAs"
         wep_key2=45342312ab
         wep_key3=45342312ab45342312ab34ac12
         wep_tx_keyidx=1
diff --git a/tests/tests/wifi/assets/BackupV1.0Format.xml b/tests/tests/wifi/assets/BackupV1.0Format.xml
index b68bdbe..84adbe3 100644
--- a/tests/tests/wifi/assets/BackupV1.0Format.xml
+++ b/tests/tests/wifi/assets/BackupV1.0Format.xml
@@ -8,8 +8,8 @@
 <string name="SSID">&quot;TestSsid1&quot;</string>
 <null name="PreSharedKey" />
 <string-array name="WEPKeys" num="4">
-<item value="&quot;WepAscii1&quot;" />
-<item value="&quot;WepAscii2&quot;" />
+<item value="&quot;WepAscii12345&quot;" />
+<item value="&quot;WepAs&quot;" />
 <item value="45342312ab" />
 <item value="45342312ab45342312ab34ac12" />
 </string-array>
diff --git a/tests/tests/wifi/assets/BackupV1.1Format.xml b/tests/tests/wifi/assets/BackupV1.1Format.xml
index 1fc9360..c28f22e 100644
--- a/tests/tests/wifi/assets/BackupV1.1Format.xml
+++ b/tests/tests/wifi/assets/BackupV1.1Format.xml
@@ -8,8 +8,8 @@
 <string name="SSID">&quot;TestSsid1&quot;</string>
 <null name="PreSharedKey" />
 <string-array name="WEPKeys" num="4">
-<item value="&quot;WepAscii1&quot;" />
-<item value="&quot;WepAscii2&quot;" />
+<item value="&quot;WepAscii12345&quot;" />
+<item value="&quot;WepAs&quot;" />
 <item value="45342312ab" />
 <item value="45342312ab45342312ab34ac12" />
 </string-array>
diff --git a/tests/tests/wifi/assets/BackupV1.2Format.xml b/tests/tests/wifi/assets/BackupV1.2Format.xml
index c55a5a7..411918a 100644
--- a/tests/tests/wifi/assets/BackupV1.2Format.xml
+++ b/tests/tests/wifi/assets/BackupV1.2Format.xml
@@ -8,8 +8,8 @@
 <string name="SSID">&quot;TestSsid1&quot;</string>
 <null name="PreSharedKey" />
 <string-array name="WEPKeys" num="4">
-<item value="&quot;WepAscii1&quot;" />
-<item value="&quot;WepAscii2&quot;" />
+<item value="&quot;WepAscii12345&quot;" />
+<item value="&quot;WepAs&quot;" />
 <item value="45342312ab" />
 <item value="45342312ab45342312ab34ac12" />
 </string-array>
diff --git a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index aab3641..268650b 100644
--- a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -200,6 +200,7 @@
         static final int ON_MESSAGE_SEND_SUCCEEDED = 6;
         static final int ON_MESSAGE_SEND_FAILED = 7;
         static final int ON_MESSAGE_RECEIVED = 8;
+        static final int ON_SESSION_DISCOVERED_LOST = 9;
 
         private final Object mLocalLock = new Object();
 
@@ -268,6 +269,11 @@
             processCallback(ON_MESSAGE_RECEIVED);
         }
 
+        @Override
+        public void onServiceLost(PeerHandle peerHandle) {
+            processCallback(ON_SESSION_DISCOVERED_LOST);
+        }
+
         /**
          * Wait for the specified callback - any of the ON_* constants. Returns a true
          * on success (specified callback triggered) or false on failure (timed-out or
@@ -480,6 +486,7 @@
 
         WifiAwareSession session = attachAndGetSession();
         session.close();
+        assertFalse(mWifiAwareManager.isDeviceAttached());
     }
 
     /**
@@ -542,6 +549,10 @@
                 discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
         PublishDiscoverySession discoverySession = discoveryCb.getPublishDiscoverySession();
         assertNotNull("Publish session", discoverySession);
+        assertFalse(discoveryCb.waitForCallback(
+                DiscoverySessionCallbackTest.ON_SERVICE_DISCOVERED));
+        assertFalse(discoveryCb.waitForCallback(
+                DiscoverySessionCallbackTest.ON_SESSION_DISCOVERED_LOST));
 
         // 2. update-publish
         publishConfig = new PublishConfig.Builder().setServiceName(
@@ -626,6 +637,10 @@
                 discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_SUBSCRIBE_STARTED));
         SubscribeDiscoverySession discoverySession = discoveryCb.getSubscribeDiscoverySession();
         assertNotNull("Subscribe session", discoverySession);
+        assertFalse(discoveryCb.waitForCallback(
+                DiscoverySessionCallbackTest.ON_SERVICE_DISCOVERED));
+        assertFalse(discoveryCb.waitForCallback(
+                DiscoverySessionCallbackTest.ON_SESSION_DISCOVERED_LOST));
 
         // 2. update-subscribe
         subscribeConfig = new SubscribeConfig.Builder().setServiceName(
@@ -873,6 +888,7 @@
 
         WifiAwareSession session = attachCb.getSession();
         assertNotNull("Wi-Fi Aware session", session);
+        assertTrue(mWifiAwareManager.isDeviceAttached());
 
         return session;
     }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java b/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
index b79bd16..d2700ec 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
@@ -38,6 +38,7 @@
 
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 @SmallTest
+@VirtualDeviceNotSupported
 public class EasyConnectStatusCallbackTest extends WifiJUnit3TestBase {
     private static final String TEST_SSID = "\"testSsid\"";
     private static final String TEST_PASSPHRASE = "\"testPassword\"";
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/FakeKeys.java b/tests/tests/wifi/src/android/net/wifi/cts/FakeKeys.java
index f875301..2c0496a 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/FakeKeys.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/FakeKeys.java
@@ -234,6 +234,35 @@
     };
     public static final PrivateKey RSA_KEY1 = loadPrivateRSAKey(FAKE_RSA_KEY_1);
 
+    private static final String CLIENT_SUITE_B_RSA3072_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIERzCCAq8CFDopjyNgaj+c2TN2k06h7okEWpHJMA0GCSqGSIb3DQEBDAUAMF4x\n"
+                    + "CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQK\n"
+                    + "DAdBbmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTESMBAGA1UEAwwJdW5pdGVzdENBMB4X\n"
+                    + "DTIwMDcyMTAyMjkxMVoXDTMwMDUzMDAyMjkxMVowYjELMAkGA1UEBhMCVVMxCzAJ\n"
+                    + "BgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNVBAoMB0FuZHJvaWQxDjAMBgNV\n"
+                    + "BAsMBVdpLUZpMRYwFAYDVQQDDA11bml0ZXN0Q2xpZW50MIIBojANBgkqhkiG9w0B\n"
+                    + "AQEFAAOCAY8AMIIBigKCAYEAwSK3C5K5udtCKTnE14e8z2cZvwmB4Xe+a8+7QLud\n"
+                    + "Hooc/lQzClgK4MbVUC0D3FE+U32C78SxKoTaRWtvPmNm+UaFT8KkwyUno/dv+2XD\n"
+                    + "pd/zARQ+3FwAfWopAhEyCVSxwsCa+slQ4juRIMIuUC1Mm0NaptZyM3Tj/ICQEfpk\n"
+                    + "o9qVIbiK6eoJMTkY8EWfAn7RTFdfR1OLuO0mVOjgLW9/+upYv6hZ19nAMAxw4QTJ\n"
+                    + "x7lLwALX7B+tDYNEZHDqYL2zyvQWAj2HClere8QYILxkvktgBg2crEJJe4XbDH7L\n"
+                    + "A3rrXmsiqf1ZbfFFEzK9NFqovL+qGh+zIP+588ShJFO9H/RDnDpiTnAFTWXQdTwg\n"
+                    + "szSS0Vw2PB+JqEABAa9DeMvXT1Oy+NY3ItPHyy63nQZVI2rXANw4NhwS0Z6DF+Qs\n"
+                    + "TNrj+GU7e4SG/EGR8SvldjYfQTWFLg1l/UT1hOOkQZwdsaW1zgKyeuiFB2KdMmbA\n"
+                    + "Sq+Ux1L1KICo0IglwWcB/8nnAgMBAAEwDQYJKoZIhvcNAQEMBQADggGBAMYwJkNw\n"
+                    + "BaCviKFmReDTMwWPRy4AMNViEeqAXgERwDEKwM7efjsaj5gctWfKsxX6UdLzkhgg\n"
+                    + "6S/T6PxVWKzJ6l7SoOuTa6tMQOZp+h3R1mdfEQbw8B5cXBxZ+batzAai6Fiy1FKS\n"
+                    + "/ka3INbcGfYuIYghfTrb4/NJKN06ZaQ1bpPwq0e4gN7800T2nbawvSf7r+8ZLcG3\n"
+                    + "6bGCjRMwDSIipNvOwoj3TG315XC7TccX5difQ4sKOY+d2MkVJ3RiO0Ciw2ZbEW8d\n"
+                    + "1FH5vUQJWnBUfSFznosGzLwH3iWfqlP+27jNE+qB2igEwCRFgVAouURx5ou43xuX\n"
+                    + "qf6JkdI3HTJGLIWxkp7gOeln4dEaYzKjYw+P0VqJvKVqQ0IXiLjHgE0J9p0vgyD6\n"
+                    + "HVVcP7U8RgqrbIjL1QgHU4KBhGi+WSUh/mRplUCNvHgcYdcHi/gHpj/j6ubwqIGV\n"
+                    + "z4iSolAHYTmBWcLyE0NgpzE6ntp+53r2KaUJA99l2iGVzbWTwqPSm0XAVw==\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_SUITE_B_RSA3072_CERT =
+            loadCertificate(CLIENT_SUITE_B_RSA3072_CERT_STRING);
+
     private static X509Certificate loadCertificate(String blob) {
         try {
             final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
index 0dfeda8..98ba803 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
@@ -242,6 +242,7 @@
         }
    }
 
+    @VirtualDeviceNotSupported
     public void testScanResultTimeStamp() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/VirtualDeviceNotSupported.java b/tests/tests/wifi/src/android/net/wifi/cts/VirtualDeviceNotSupported.java
new file mode 100644
index 0000000..6c23f38f
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/VirtualDeviceNotSupported.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Annotation for tests that don't pass on virtual devices (i.e. in presubmit). */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface VirtualDeviceNotSupported {}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
index 1d2bd2a..1b224ca 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
@@ -287,8 +287,8 @@
         configuration.SSID = "\"TestSsid1\"";
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         configuration.wepKeys = new String[4];
-        configuration.wepKeys[0] = "\"WepAscii1\"";
-        configuration.wepKeys[1] = "\"WepAscii2\"";
+        configuration.wepKeys[0] = "\"WepAscii12345\"";
+        configuration.wepKeys[1] = "\"WepAs\"";
         configuration.wepKeys[2] = "45342312ab";
         configuration.wepKeys[3] = "45342312ab45342312ab34ac12";
         configuration.wepTxKeyIndex = 1;
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
index 554e1ce..6817144 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
@@ -16,13 +16,23 @@
 
 package android.net.wifi.cts;
 
-import java.util.List;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OPEN;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OWE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_PSK;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_SAE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_WAPI_CERT;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_WAPI_PSK;
 
 import android.content.Context;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
+
+import androidx.test.filters.SdkSuppress;
+
+import java.util.List;
 
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class WifiConfigurationTest extends WifiJUnit3TestBase {
@@ -48,4 +58,68 @@
             }
         }
     }
+
+    // TODO(b/167575586): Wait for S SDK finalization to change minSdkVersion to
+    // Build.VERSION_CODES.S
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testGetAuthType() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+
+        configuration.setSecurityParams(SECURITY_TYPE_PSK);
+        assertEquals(WifiConfiguration.KeyMgmt.WPA_PSK, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_SAE);
+        assertEquals(WifiConfiguration.KeyMgmt.SAE, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_WAPI_PSK);
+        assertEquals(WifiConfiguration.KeyMgmt.WAPI_PSK, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_OPEN);
+        assertEquals(WifiConfiguration.KeyMgmt.NONE, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_OWE);
+        assertEquals(WifiConfiguration.KeyMgmt.OWE, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_EAP);
+        assertEquals(WifiConfiguration.KeyMgmt.WPA_EAP, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B);
+        assertEquals(WifiConfiguration.KeyMgmt.SUITE_B_192, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_WAPI_CERT);
+        assertEquals(WifiConfiguration.KeyMgmt.WAPI_CERT, configuration.getAuthType());
+    }
+
+    // TODO(b/167575586): Wait for S SDK finalization to change minSdkVersion to
+    // Build.VERSION_CODES.S
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testGetAuthTypeFailurePsk8021X() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+
+        configuration.setSecurityParams(SECURITY_TYPE_PSK);
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+        try {
+            configuration.getAuthType();
+            fail("Expected IllegalStateException exception");
+        } catch(IllegalStateException e) {
+            // empty
+        }
+    }
+
+    // TODO(b/167575586): Wait for S SDK finalization to change minSdkVersion to
+    // Build.VERSION_CODES.S
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testGetAuthTypeFailure8021xEapSae() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+        try {
+            configuration.getAuthType();
+            fail("Expected IllegalStateException exception");
+        } catch(IllegalStateException e) {
+            // empty
+        }
+    }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
index 22124eb..643f42d 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
@@ -233,7 +233,7 @@
                 .build();
 
         // different instances
-        assertThat(info1).isNotSameAs(info2);
+        assertThat(info1).isNotSameInstanceAs(info2);
 
         // assert that info1 didn't change
         assertThat(info1.getSSID()).isEqualTo("\"" + TEST_SSID + "\"");
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
index 8627517..d4fcafb 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -59,6 +59,7 @@
 import android.net.wifi.hotspot2.ProvisioningCallback;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
@@ -74,6 +75,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.PollingCheck;
@@ -513,6 +515,7 @@
      * To run this test in cts-tradefed:
      * run cts --class android.net.wifi.cts.WifiManagerTest --method testWifiScanTimestamp
      */
+    @VirtualDeviceNotSupported
     public void testWifiScanTimestamp() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             Log.d(TAG, "Skipping test as WiFi is not supported");
@@ -2009,6 +2012,7 @@
     /**
      * Tests {@link WifiManager#getFactoryMacAddresses()} returns at least one valid MAC address.
      */
+    @VirtualDeviceNotSupported
     public void testGetFactoryMacAddresses() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -3030,6 +3034,17 @@
 
     }
 
+    // TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion?
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testIsMultiStaConcurrencySupported() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        // ensure no crash.
+        mWifiManager.isStaApConcurrencySupported();
+    }
+
     private PasspointConfiguration getTargetPasspointConfiguration(
             List<PasspointConfiguration> configurationList, String uniqueId) {
         if (configurationList == null || configurationList.isEmpty()) {
@@ -3042,4 +3057,58 @@
         }
         return null;
     }
+
+    /**
+     * Test that {@link WifiManager#is60GHzBandSupported()} throws UnsupportedOperationException
+     * if the release is older than S.
+     */
+    // TODO(b/167575586): Wait for S SDK finalization before changing
+    // to `maxSdkVersion = Build.VERSION_CODES.R`
+    @SdkSuppress(maxSdkVersion = -1, codeName = "REL")
+    public void testIs60GhzBandSupportedOnROrOlder() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        // check for 60ghz support with wifi enabled
+        try {
+            boolean isSupported = mWifiManager.is60GHzBandSupported();
+            fail("Expected UnsupportedOperationException");
+        } catch (UnsupportedOperationException ex) {
+        }
+
+    }
+
+    /**
+     * Test that {@link WifiManager#is60GHzBandSupported()} returns successfully in
+     * both Wifi enabled/disabled states for release newer than R.
+     * Note that the response depends on device support and hence both true/false
+     * are valid responses.
+     */
+    // TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testIs60GhzBandSupportedOnSOrNewer() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        // check for 60ghz support with wifi enabled
+        boolean isSupportedWhenWifiEnabled = mWifiManager.is60GHzBandSupported();
+
+        // Check for 60GHz support with wifi disabled
+        setWifiEnabled(false);
+        PollingCheck.check(
+                "Wifi not disabled!",
+                20000,
+                () -> !mWifiManager.isWifiEnabled());
+        boolean isSupportedWhenWifiDisabled = mWifiManager.is60GHzBandSupported();
+
+        // If Support is true when WiFi is disable, then it has to be true when it is enabled.
+        // Note, the reverse is a valid case.
+        if (isSupportedWhenWifiDisabled) {
+            assertTrue(isSupportedWhenWifiEnabled);
+        }
+    }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
index 34a36d6..e647593 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
@@ -27,16 +27,30 @@
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.platform.test.annotations.AppModeFull;
 import android.telephony.TelephonyManager;
-import android.test.AndroidTestCase;
 
+import androidx.core.os.BuildCompat;
 import androidx.test.filters.SmallTest;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 @SmallTest
 public class WifiNetworkSuggestionTest extends WifiJUnit3TestBase {
     private static final String TEST_SSID = "testSsid";
     private static final String TEST_BSSID = "00:df:aa:bc:12:23";
     private static final String TEST_PASSPHRASE = "testPassword";
+    private static final int TEST_PRIORITY = 5;
+    private static final int TEST_PRIORITY_GROUP = 1;
+    private static final int TEST_SUB_ID = 1;
 
     @Override
     protected void setUp() throws Exception {
@@ -57,6 +71,463 @@
         super.tearDown();
     }
 
+    private static final String CA_SUITE_B_RSA3072_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIEnTCCAwWgAwIBAgIUD87Y8fFLzLr1HQ/64aEnjNq2R/4wDQYJKoZIhvcNAQEM\n"
+                    + "BQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAO\n"
+                    + "BgNVBAoMB0FuZHJvaWQxDjAMBgNVBAsMBVdpLUZpMRIwEAYDVQQDDAl1bml0ZXN0\n"
+                    + "Q0EwHhcNMjAwNzIxMDIxNzU0WhcNMzAwNTMwMDIxNzU0WjBeMQswCQYDVQQGEwJV\n"
+                    + "UzELMAkGA1UECAwCQ0ExDDAKBgNVBAcMA01UVjEQMA4GA1UECgwHQW5kcm9pZDEO\n"
+                    + "MAwGA1UECwwFV2ktRmkxEjAQBgNVBAMMCXVuaXRlc3RDQTCCAaIwDQYJKoZIhvcN\n"
+                    + "AQEBBQADggGPADCCAYoCggGBAMtrsT0otlxh0QS079KpRRbU1PQjCihSoltXnrxF\n"
+                    + "sTWZs2weVEeYVyYU5LaauCDDgISCMtjtfbfylMBeYjpWB5hYzYQOiTzo0anWhMyb\n"
+                    + "Ngb7gpMVZuIl6lwMYRyVRKwHWnTo2EUg1ZzW5rGe5fs/KHj6//hoNFm+3Oju0TQd\n"
+                    + "nraQULpoERPF5B7p85Cssk8uNbviBfZXvtCuJ4N6w7PNceOY/9bbwc1mC+pPZmzV\n"
+                    + "SOAg0vvbIQRzChm63C3jBC3xmxSOOZVrKN4zKDG2s8P0oCNGt0NlgRMrgbPRekzg\n"
+                    + "4avkbA0vTuc2AyriTEYkdea/Mt4EpRg9XuOb43U/GJ/d/vQv2/9fsxhXmsZrn8kr\n"
+                    + "Qo5MMHJFUd96GgHmvYSU3Mf/5r8gF626lvqHioGuTAuHUSnr02ri1WUxZ15LDRgY\n"
+                    + "quMjDCFZfucjJPDAdtiHcFSej/4SLJlN39z8oKKNPn3aL9Gv49oAKs9S8tfDVzMk\n"
+                    + "fDLROQFHFuW715GnnMgEAoOpRwIDAQABo1MwUTAdBgNVHQ4EFgQUeVuGmSVN4ARs\n"
+                    + "mesUMWSJ2qWLbxUwHwYDVR0jBBgwFoAUeVuGmSVN4ARsmesUMWSJ2qWLbxUwDwYD\n"
+                    + "VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQwFAAOCAYEAit1Lo/hegZpPuT9dlWZJ\n"
+                    + "bC8JvAf95O8lnn6LFb69pgYOHCLgCIlvYXu9rdBUJgZo+V1MzJJljiO6RxWRfKbQ\n"
+                    + "8WBYkoqR1EqriR3Kn8q/SjIZCdFSaznTyU1wQMveBQ6RJWXSUhYVfE9RjyFTp7B4\n"
+                    + "UyH2uCluR/0T06HQNGfH5XpIYQqCk1Zgng5lmEmheLDPoJpa92lKeQFJMC6eYz9g\n"
+                    + "lF1GHxPxkPfbMJ6ZDp5X6Yopu6Q6uEXhVKM/iQVcgzRkx9rid+xTYl+nOKyK/XfC\n"
+                    + "z8P0/TFIoPTW02DLge5wKagdoCpy1B7HdrAXyUjoH4B8MsUkq3kYPFSjPzScuTtV\n"
+                    + "kUuDw5ipCNeXCRnhbYqRDk6PX5GUu2cmN9jtaH3tbgm3fKNOsd/BO1fLIl7qjXlR\n"
+                    + "27HHbC0JXjNvlm2DLp23v4NTxS7WZGYsxyUj5DZrxBxqCsTXu/01w1BrQKWKh9FM\n"
+                    + "aVrlA8omfVODK2CSuw+KhEMHepRv/AUgsLl4L4+RMoa+\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CA_SUITE_B_RSA3072_CERT =
+            loadCertificate(CA_SUITE_B_RSA3072_CERT_STRING);
+
+    private static final String CA_SUITE_B_ECDSA_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIICTzCCAdSgAwIBAgIUdnLttwNPnQzFufplGOr9bTrGCqMwCgYIKoZIzj0EAwMw\n"
+                    + "XjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNV\n"
+                    + "BAoMB0FuZHJvaWQxDjAMBgNVBAsMBVdpLUZpMRIwEAYDVQQDDAl1bml0ZXN0Q0Ew\n"
+                    + "HhcNMjAwNzIxMDIyNDA1WhcNMzAwNTMwMDIyNDA1WjBeMQswCQYDVQQGEwJVUzEL\n"
+                    + "MAkGA1UECAwCQ0ExDDAKBgNVBAcMA01UVjEQMA4GA1UECgwHQW5kcm9pZDEOMAwG\n"
+                    + "A1UECwwFV2ktRmkxEjAQBgNVBAMMCXVuaXRlc3RDQTB2MBAGByqGSM49AgEGBSuB\n"
+                    + "BAAiA2IABFmntXwk9icqhDQFUP1xy04WyEpaGW4q6Q+8pujlSl/X3iotPZ++GZfp\n"
+                    + "Mfv3YDHDBl6sELPQ2BEjyPXmpsKjOUdiUe69e88oGEdeqT2xXiQ6uzpTfJD4170i\n"
+                    + "O/TwLrQGKKNTMFEwHQYDVR0OBBYEFCjptsX3g4g5W0L4oEP6N3gfyiZXMB8GA1Ud\n"
+                    + "IwQYMBaAFCjptsX3g4g5W0L4oEP6N3gfyiZXMA8GA1UdEwEB/wQFMAMBAf8wCgYI\n"
+                    + "KoZIzj0EAwMDaQAwZgIxAK61brUYRbLmQKiaEboZgrHtnPAcGo7Yzx3MwHecx3Dm\n"
+                    + "5soIeLVYc8bPYN1pbhXW1gIxALdEe2sh03nBHyQH4adYoZungoCwt8mp/7sJFxou\n"
+                    + "9UnRegyBgGzf74ROWdpZHzh+Pg==\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CA_SUITE_B_ECDSA_CERT =
+            loadCertificate(CA_SUITE_B_ECDSA_CERT_STRING);
+
+    private static final String CLIENT_SUITE_B_RSA3072_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIERzCCAq8CFDopjyNgaj+c2TN2k06h7okEWpHJMA0GCSqGSIb3DQEBDAUAMF4x\n"
+                    + "CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQK\n"
+                    + "DAdBbmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTESMBAGA1UEAwwJdW5pdGVzdENBMB4X\n"
+                    + "DTIwMDcyMTAyMjkxMVoXDTMwMDUzMDAyMjkxMVowYjELMAkGA1UEBhMCVVMxCzAJ\n"
+                    + "BgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNVBAoMB0FuZHJvaWQxDjAMBgNV\n"
+                    + "BAsMBVdpLUZpMRYwFAYDVQQDDA11bml0ZXN0Q2xpZW50MIIBojANBgkqhkiG9w0B\n"
+                    + "AQEFAAOCAY8AMIIBigKCAYEAwSK3C5K5udtCKTnE14e8z2cZvwmB4Xe+a8+7QLud\n"
+                    + "Hooc/lQzClgK4MbVUC0D3FE+U32C78SxKoTaRWtvPmNm+UaFT8KkwyUno/dv+2XD\n"
+                    + "pd/zARQ+3FwAfWopAhEyCVSxwsCa+slQ4juRIMIuUC1Mm0NaptZyM3Tj/ICQEfpk\n"
+                    + "o9qVIbiK6eoJMTkY8EWfAn7RTFdfR1OLuO0mVOjgLW9/+upYv6hZ19nAMAxw4QTJ\n"
+                    + "x7lLwALX7B+tDYNEZHDqYL2zyvQWAj2HClere8QYILxkvktgBg2crEJJe4XbDH7L\n"
+                    + "A3rrXmsiqf1ZbfFFEzK9NFqovL+qGh+zIP+588ShJFO9H/RDnDpiTnAFTWXQdTwg\n"
+                    + "szSS0Vw2PB+JqEABAa9DeMvXT1Oy+NY3ItPHyy63nQZVI2rXANw4NhwS0Z6DF+Qs\n"
+                    + "TNrj+GU7e4SG/EGR8SvldjYfQTWFLg1l/UT1hOOkQZwdsaW1zgKyeuiFB2KdMmbA\n"
+                    + "Sq+Ux1L1KICo0IglwWcB/8nnAgMBAAEwDQYJKoZIhvcNAQEMBQADggGBAMYwJkNw\n"
+                    + "BaCviKFmReDTMwWPRy4AMNViEeqAXgERwDEKwM7efjsaj5gctWfKsxX6UdLzkhgg\n"
+                    + "6S/T6PxVWKzJ6l7SoOuTa6tMQOZp+h3R1mdfEQbw8B5cXBxZ+batzAai6Fiy1FKS\n"
+                    + "/ka3INbcGfYuIYghfTrb4/NJKN06ZaQ1bpPwq0e4gN7800T2nbawvSf7r+8ZLcG3\n"
+                    + "6bGCjRMwDSIipNvOwoj3TG315XC7TccX5difQ4sKOY+d2MkVJ3RiO0Ciw2ZbEW8d\n"
+                    + "1FH5vUQJWnBUfSFznosGzLwH3iWfqlP+27jNE+qB2igEwCRFgVAouURx5ou43xuX\n"
+                    + "qf6JkdI3HTJGLIWxkp7gOeln4dEaYzKjYw+P0VqJvKVqQ0IXiLjHgE0J9p0vgyD6\n"
+                    + "HVVcP7U8RgqrbIjL1QgHU4KBhGi+WSUh/mRplUCNvHgcYdcHi/gHpj/j6ubwqIGV\n"
+                    + "z4iSolAHYTmBWcLyE0NgpzE6ntp+53r2KaUJA99l2iGVzbWTwqPSm0XAVw==\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_SUITE_B_RSA3072_CERT =
+            loadCertificate(CLIENT_SUITE_B_RSA3072_CERT_STRING);
+
+    private static final byte[] CLIENT_SUITE_B_RSA3072_KEY_DATA = new byte[]{
+            (byte) 0x30, (byte) 0x82, (byte) 0x06, (byte) 0xfe, (byte) 0x02, (byte) 0x01,
+            (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
+            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
+            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
+            (byte) 0x06, (byte) 0xe8, (byte) 0x30, (byte) 0x82, (byte) 0x06, (byte) 0xe4,
+            (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x82, (byte) 0x01,
+            (byte) 0x81, (byte) 0x00, (byte) 0xc1, (byte) 0x22, (byte) 0xb7, (byte) 0x0b,
+            (byte) 0x92, (byte) 0xb9, (byte) 0xb9, (byte) 0xdb, (byte) 0x42, (byte) 0x29,
+            (byte) 0x39, (byte) 0xc4, (byte) 0xd7, (byte) 0x87, (byte) 0xbc, (byte) 0xcf,
+            (byte) 0x67, (byte) 0x19, (byte) 0xbf, (byte) 0x09, (byte) 0x81, (byte) 0xe1,
+            (byte) 0x77, (byte) 0xbe, (byte) 0x6b, (byte) 0xcf, (byte) 0xbb, (byte) 0x40,
+            (byte) 0xbb, (byte) 0x9d, (byte) 0x1e, (byte) 0x8a, (byte) 0x1c, (byte) 0xfe,
+            (byte) 0x54, (byte) 0x33, (byte) 0x0a, (byte) 0x58, (byte) 0x0a, (byte) 0xe0,
+            (byte) 0xc6, (byte) 0xd5, (byte) 0x50, (byte) 0x2d, (byte) 0x03, (byte) 0xdc,
+            (byte) 0x51, (byte) 0x3e, (byte) 0x53, (byte) 0x7d, (byte) 0x82, (byte) 0xef,
+            (byte) 0xc4, (byte) 0xb1, (byte) 0x2a, (byte) 0x84, (byte) 0xda, (byte) 0x45,
+            (byte) 0x6b, (byte) 0x6f, (byte) 0x3e, (byte) 0x63, (byte) 0x66, (byte) 0xf9,
+            (byte) 0x46, (byte) 0x85, (byte) 0x4f, (byte) 0xc2, (byte) 0xa4, (byte) 0xc3,
+            (byte) 0x25, (byte) 0x27, (byte) 0xa3, (byte) 0xf7, (byte) 0x6f, (byte) 0xfb,
+            (byte) 0x65, (byte) 0xc3, (byte) 0xa5, (byte) 0xdf, (byte) 0xf3, (byte) 0x01,
+            (byte) 0x14, (byte) 0x3e, (byte) 0xdc, (byte) 0x5c, (byte) 0x00, (byte) 0x7d,
+            (byte) 0x6a, (byte) 0x29, (byte) 0x02, (byte) 0x11, (byte) 0x32, (byte) 0x09,
+            (byte) 0x54, (byte) 0xb1, (byte) 0xc2, (byte) 0xc0, (byte) 0x9a, (byte) 0xfa,
+            (byte) 0xc9, (byte) 0x50, (byte) 0xe2, (byte) 0x3b, (byte) 0x91, (byte) 0x20,
+            (byte) 0xc2, (byte) 0x2e, (byte) 0x50, (byte) 0x2d, (byte) 0x4c, (byte) 0x9b,
+            (byte) 0x43, (byte) 0x5a, (byte) 0xa6, (byte) 0xd6, (byte) 0x72, (byte) 0x33,
+            (byte) 0x74, (byte) 0xe3, (byte) 0xfc, (byte) 0x80, (byte) 0x90, (byte) 0x11,
+            (byte) 0xfa, (byte) 0x64, (byte) 0xa3, (byte) 0xda, (byte) 0x95, (byte) 0x21,
+            (byte) 0xb8, (byte) 0x8a, (byte) 0xe9, (byte) 0xea, (byte) 0x09, (byte) 0x31,
+            (byte) 0x39, (byte) 0x18, (byte) 0xf0, (byte) 0x45, (byte) 0x9f, (byte) 0x02,
+            (byte) 0x7e, (byte) 0xd1, (byte) 0x4c, (byte) 0x57, (byte) 0x5f, (byte) 0x47,
+            (byte) 0x53, (byte) 0x8b, (byte) 0xb8, (byte) 0xed, (byte) 0x26, (byte) 0x54,
+            (byte) 0xe8, (byte) 0xe0, (byte) 0x2d, (byte) 0x6f, (byte) 0x7f, (byte) 0xfa,
+            (byte) 0xea, (byte) 0x58, (byte) 0xbf, (byte) 0xa8, (byte) 0x59, (byte) 0xd7,
+            (byte) 0xd9, (byte) 0xc0, (byte) 0x30, (byte) 0x0c, (byte) 0x70, (byte) 0xe1,
+            (byte) 0x04, (byte) 0xc9, (byte) 0xc7, (byte) 0xb9, (byte) 0x4b, (byte) 0xc0,
+            (byte) 0x02, (byte) 0xd7, (byte) 0xec, (byte) 0x1f, (byte) 0xad, (byte) 0x0d,
+            (byte) 0x83, (byte) 0x44, (byte) 0x64, (byte) 0x70, (byte) 0xea, (byte) 0x60,
+            (byte) 0xbd, (byte) 0xb3, (byte) 0xca, (byte) 0xf4, (byte) 0x16, (byte) 0x02,
+            (byte) 0x3d, (byte) 0x87, (byte) 0x0a, (byte) 0x57, (byte) 0xab, (byte) 0x7b,
+            (byte) 0xc4, (byte) 0x18, (byte) 0x20, (byte) 0xbc, (byte) 0x64, (byte) 0xbe,
+            (byte) 0x4b, (byte) 0x60, (byte) 0x06, (byte) 0x0d, (byte) 0x9c, (byte) 0xac,
+            (byte) 0x42, (byte) 0x49, (byte) 0x7b, (byte) 0x85, (byte) 0xdb, (byte) 0x0c,
+            (byte) 0x7e, (byte) 0xcb, (byte) 0x03, (byte) 0x7a, (byte) 0xeb, (byte) 0x5e,
+            (byte) 0x6b, (byte) 0x22, (byte) 0xa9, (byte) 0xfd, (byte) 0x59, (byte) 0x6d,
+            (byte) 0xf1, (byte) 0x45, (byte) 0x13, (byte) 0x32, (byte) 0xbd, (byte) 0x34,
+            (byte) 0x5a, (byte) 0xa8, (byte) 0xbc, (byte) 0xbf, (byte) 0xaa, (byte) 0x1a,
+            (byte) 0x1f, (byte) 0xb3, (byte) 0x20, (byte) 0xff, (byte) 0xb9, (byte) 0xf3,
+            (byte) 0xc4, (byte) 0xa1, (byte) 0x24, (byte) 0x53, (byte) 0xbd, (byte) 0x1f,
+            (byte) 0xf4, (byte) 0x43, (byte) 0x9c, (byte) 0x3a, (byte) 0x62, (byte) 0x4e,
+            (byte) 0x70, (byte) 0x05, (byte) 0x4d, (byte) 0x65, (byte) 0xd0, (byte) 0x75,
+            (byte) 0x3c, (byte) 0x20, (byte) 0xb3, (byte) 0x34, (byte) 0x92, (byte) 0xd1,
+            (byte) 0x5c, (byte) 0x36, (byte) 0x3c, (byte) 0x1f, (byte) 0x89, (byte) 0xa8,
+            (byte) 0x40, (byte) 0x01, (byte) 0x01, (byte) 0xaf, (byte) 0x43, (byte) 0x78,
+            (byte) 0xcb, (byte) 0xd7, (byte) 0x4f, (byte) 0x53, (byte) 0xb2, (byte) 0xf8,
+            (byte) 0xd6, (byte) 0x37, (byte) 0x22, (byte) 0xd3, (byte) 0xc7, (byte) 0xcb,
+            (byte) 0x2e, (byte) 0xb7, (byte) 0x9d, (byte) 0x06, (byte) 0x55, (byte) 0x23,
+            (byte) 0x6a, (byte) 0xd7, (byte) 0x00, (byte) 0xdc, (byte) 0x38, (byte) 0x36,
+            (byte) 0x1c, (byte) 0x12, (byte) 0xd1, (byte) 0x9e, (byte) 0x83, (byte) 0x17,
+            (byte) 0xe4, (byte) 0x2c, (byte) 0x4c, (byte) 0xda, (byte) 0xe3, (byte) 0xf8,
+            (byte) 0x65, (byte) 0x3b, (byte) 0x7b, (byte) 0x84, (byte) 0x86, (byte) 0xfc,
+            (byte) 0x41, (byte) 0x91, (byte) 0xf1, (byte) 0x2b, (byte) 0xe5, (byte) 0x76,
+            (byte) 0x36, (byte) 0x1f, (byte) 0x41, (byte) 0x35, (byte) 0x85, (byte) 0x2e,
+            (byte) 0x0d, (byte) 0x65, (byte) 0xfd, (byte) 0x44, (byte) 0xf5, (byte) 0x84,
+            (byte) 0xe3, (byte) 0xa4, (byte) 0x41, (byte) 0x9c, (byte) 0x1d, (byte) 0xb1,
+            (byte) 0xa5, (byte) 0xb5, (byte) 0xce, (byte) 0x02, (byte) 0xb2, (byte) 0x7a,
+            (byte) 0xe8, (byte) 0x85, (byte) 0x07, (byte) 0x62, (byte) 0x9d, (byte) 0x32,
+            (byte) 0x66, (byte) 0xc0, (byte) 0x4a, (byte) 0xaf, (byte) 0x94, (byte) 0xc7,
+            (byte) 0x52, (byte) 0xf5, (byte) 0x28, (byte) 0x80, (byte) 0xa8, (byte) 0xd0,
+            (byte) 0x88, (byte) 0x25, (byte) 0xc1, (byte) 0x67, (byte) 0x01, (byte) 0xff,
+            (byte) 0xc9, (byte) 0xe7, (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00,
+            (byte) 0x01, (byte) 0x02, (byte) 0x82, (byte) 0x01, (byte) 0x80, (byte) 0x04,
+            (byte) 0xb1, (byte) 0xcc, (byte) 0x53, (byte) 0x3a, (byte) 0xb0, (byte) 0xcb,
+            (byte) 0x04, (byte) 0xba, (byte) 0x59, (byte) 0xf8, (byte) 0x2e, (byte) 0x81,
+            (byte) 0xb2, (byte) 0xa9, (byte) 0xf3, (byte) 0x3c, (byte) 0xa5, (byte) 0x52,
+            (byte) 0x90, (byte) 0x6f, (byte) 0x98, (byte) 0xc4, (byte) 0x69, (byte) 0x5b,
+            (byte) 0x83, (byte) 0x84, (byte) 0x20, (byte) 0xb1, (byte) 0xae, (byte) 0xc3,
+            (byte) 0x04, (byte) 0x46, (byte) 0x6a, (byte) 0x24, (byte) 0x2f, (byte) 0xcd,
+            (byte) 0x6b, (byte) 0x90, (byte) 0x70, (byte) 0x20, (byte) 0x45, (byte) 0x25,
+            (byte) 0x1a, (byte) 0xc3, (byte) 0x02, (byte) 0x42, (byte) 0xf3, (byte) 0x49,
+            (byte) 0xe2, (byte) 0x3e, (byte) 0x21, (byte) 0x87, (byte) 0xdd, (byte) 0x6a,
+            (byte) 0x94, (byte) 0x2a, (byte) 0x1e, (byte) 0x0f, (byte) 0xdb, (byte) 0x77,
+            (byte) 0x5f, (byte) 0xc1, (byte) 0x2c, (byte) 0x03, (byte) 0xfb, (byte) 0xcf,
+            (byte) 0x91, (byte) 0x82, (byte) 0xa1, (byte) 0xbf, (byte) 0xb0, (byte) 0x73,
+            (byte) 0xfa, (byte) 0xda, (byte) 0xbc, (byte) 0xf8, (byte) 0x9f, (byte) 0x45,
+            (byte) 0xd3, (byte) 0xe8, (byte) 0xbb, (byte) 0x38, (byte) 0xfb, (byte) 0xc2,
+            (byte) 0x2d, (byte) 0x76, (byte) 0x51, (byte) 0x96, (byte) 0x18, (byte) 0x03,
+            (byte) 0x15, (byte) 0xd9, (byte) 0xea, (byte) 0x82, (byte) 0x25, (byte) 0x83,
+            (byte) 0xff, (byte) 0x5c, (byte) 0x85, (byte) 0x06, (byte) 0x09, (byte) 0xb2,
+            (byte) 0x46, (byte) 0x12, (byte) 0x64, (byte) 0x02, (byte) 0x74, (byte) 0x4f,
+            (byte) 0xbc, (byte) 0x9a, (byte) 0x25, (byte) 0x18, (byte) 0x01, (byte) 0x07,
+            (byte) 0x17, (byte) 0x25, (byte) 0x55, (byte) 0x7c, (byte) 0xdc, (byte) 0xe1,
+            (byte) 0xd1, (byte) 0x5a, (byte) 0x2f, (byte) 0x25, (byte) 0xaf, (byte) 0xf6,
+            (byte) 0x8f, (byte) 0xa4, (byte) 0x9a, (byte) 0x5a, (byte) 0x3a, (byte) 0xfe,
+            (byte) 0x2e, (byte) 0x93, (byte) 0x24, (byte) 0xa0, (byte) 0x27, (byte) 0xac,
+            (byte) 0x07, (byte) 0x75, (byte) 0x33, (byte) 0x01, (byte) 0x54, (byte) 0x23,
+            (byte) 0x0f, (byte) 0xe8, (byte) 0x9f, (byte) 0xfa, (byte) 0x36, (byte) 0xe6,
+            (byte) 0x3a, (byte) 0xd5, (byte) 0x78, (byte) 0xb0, (byte) 0xe4, (byte) 0x6a,
+            (byte) 0x16, (byte) 0x50, (byte) 0xbd, (byte) 0x0f, (byte) 0x9f, (byte) 0x32,
+            (byte) 0xa1, (byte) 0x6b, (byte) 0xf5, (byte) 0xa4, (byte) 0x34, (byte) 0x58,
+            (byte) 0xb6, (byte) 0xa4, (byte) 0xb3, (byte) 0xc3, (byte) 0x83, (byte) 0x08,
+            (byte) 0x18, (byte) 0xc7, (byte) 0xef, (byte) 0x95, (byte) 0xe2, (byte) 0x1b,
+            (byte) 0xba, (byte) 0x35, (byte) 0x61, (byte) 0xa3, (byte) 0xb4, (byte) 0x30,
+            (byte) 0xe0, (byte) 0xd1, (byte) 0xc1, (byte) 0xa2, (byte) 0x3a, (byte) 0xc6,
+            (byte) 0xb4, (byte) 0xd2, (byte) 0x80, (byte) 0x5a, (byte) 0xaf, (byte) 0xa4,
+            (byte) 0x54, (byte) 0x3c, (byte) 0x66, (byte) 0x5a, (byte) 0x1c, (byte) 0x4d,
+            (byte) 0xe1, (byte) 0xd9, (byte) 0x98, (byte) 0x44, (byte) 0x01, (byte) 0x1b,
+            (byte) 0x8c, (byte) 0xe9, (byte) 0x80, (byte) 0x54, (byte) 0x83, (byte) 0x3d,
+            (byte) 0x96, (byte) 0x25, (byte) 0x41, (byte) 0x1c, (byte) 0xad, (byte) 0xae,
+            (byte) 0x3b, (byte) 0x7a, (byte) 0xd7, (byte) 0x9d, (byte) 0x10, (byte) 0x7c,
+            (byte) 0xd1, (byte) 0xa7, (byte) 0x96, (byte) 0x39, (byte) 0xa5, (byte) 0x2f,
+            (byte) 0xbe, (byte) 0xc3, (byte) 0x2c, (byte) 0x64, (byte) 0x01, (byte) 0xfe,
+            (byte) 0xa2, (byte) 0xd1, (byte) 0x6a, (byte) 0xcf, (byte) 0x4c, (byte) 0x76,
+            (byte) 0x3b, (byte) 0xc8, (byte) 0x35, (byte) 0x21, (byte) 0xda, (byte) 0x98,
+            (byte) 0xcf, (byte) 0xf9, (byte) 0x29, (byte) 0xff, (byte) 0x30, (byte) 0x59,
+            (byte) 0x36, (byte) 0x53, (byte) 0x0b, (byte) 0xbb, (byte) 0xfa, (byte) 0xba,
+            (byte) 0xc4, (byte) 0x03, (byte) 0x23, (byte) 0xe0, (byte) 0xd3, (byte) 0x33,
+            (byte) 0xff, (byte) 0x32, (byte) 0xdb, (byte) 0x30, (byte) 0x64, (byte) 0xc7,
+            (byte) 0x56, (byte) 0xca, (byte) 0x55, (byte) 0x14, (byte) 0xee, (byte) 0x58,
+            (byte) 0xfe, (byte) 0x96, (byte) 0x7e, (byte) 0x1c, (byte) 0x34, (byte) 0x16,
+            (byte) 0xeb, (byte) 0x76, (byte) 0x26, (byte) 0x48, (byte) 0xe2, (byte) 0xe5,
+            (byte) 0x5c, (byte) 0xd5, (byte) 0x83, (byte) 0x37, (byte) 0xd9, (byte) 0x09,
+            (byte) 0x71, (byte) 0xbc, (byte) 0x54, (byte) 0x25, (byte) 0xca, (byte) 0x2e,
+            (byte) 0xdb, (byte) 0x36, (byte) 0x39, (byte) 0xcc, (byte) 0x3a, (byte) 0x81,
+            (byte) 0x95, (byte) 0x9e, (byte) 0xf4, (byte) 0x01, (byte) 0xa7, (byte) 0xc0,
+            (byte) 0x20, (byte) 0xce, (byte) 0x70, (byte) 0x55, (byte) 0x2c, (byte) 0xe0,
+            (byte) 0x93, (byte) 0x72, (byte) 0xa6, (byte) 0x25, (byte) 0xda, (byte) 0x64,
+            (byte) 0x19, (byte) 0x18, (byte) 0xd2, (byte) 0x31, (byte) 0xe2, (byte) 0x7c,
+            (byte) 0xf2, (byte) 0x30, (byte) 0x9e, (byte) 0x8d, (byte) 0xc6, (byte) 0x14,
+            (byte) 0x8a, (byte) 0x38, (byte) 0xf0, (byte) 0x94, (byte) 0xeb, (byte) 0xf4,
+            (byte) 0x64, (byte) 0x92, (byte) 0x3d, (byte) 0x67, (byte) 0xa6, (byte) 0x2c,
+            (byte) 0x52, (byte) 0xfc, (byte) 0x60, (byte) 0xca, (byte) 0x2a, (byte) 0xcf,
+            (byte) 0x24, (byte) 0xd5, (byte) 0x42, (byte) 0x5f, (byte) 0xc7, (byte) 0x9f,
+            (byte) 0xf3, (byte) 0xb4, (byte) 0xdf, (byte) 0x76, (byte) 0x6e, (byte) 0x53,
+            (byte) 0xa1, (byte) 0x7b, (byte) 0xae, (byte) 0xa5, (byte) 0x84, (byte) 0x1f,
+            (byte) 0xfa, (byte) 0xc0, (byte) 0xb4, (byte) 0x6c, (byte) 0xc9, (byte) 0x02,
+            (byte) 0x81, (byte) 0xc1, (byte) 0x00, (byte) 0xf3, (byte) 0x17, (byte) 0xd9,
+            (byte) 0x48, (byte) 0x17, (byte) 0x87, (byte) 0x84, (byte) 0x16, (byte) 0xea,
+            (byte) 0x2d, (byte) 0x31, (byte) 0x1b, (byte) 0xce, (byte) 0xec, (byte) 0xaf,
+            (byte) 0xdc, (byte) 0x6b, (byte) 0xaf, (byte) 0xc8, (byte) 0xf1, (byte) 0x40,
+            (byte) 0xa7, (byte) 0x4f, (byte) 0xef, (byte) 0x48, (byte) 0x08, (byte) 0x5e,
+            (byte) 0x9a, (byte) 0xd1, (byte) 0xc0, (byte) 0xb1, (byte) 0xfe, (byte) 0xe7,
+            (byte) 0x03, (byte) 0xd5, (byte) 0x96, (byte) 0x01, (byte) 0xe8, (byte) 0x40,
+            (byte) 0xca, (byte) 0x78, (byte) 0xcb, (byte) 0xb3, (byte) 0x28, (byte) 0x1a,
+            (byte) 0xf0, (byte) 0xe5, (byte) 0xf6, (byte) 0x46, (byte) 0xef, (byte) 0xcd,
+            (byte) 0x1a, (byte) 0x0f, (byte) 0x13, (byte) 0x2d, (byte) 0x38, (byte) 0xf8,
+            (byte) 0xf7, (byte) 0x88, (byte) 0x21, (byte) 0x15, (byte) 0xce, (byte) 0x48,
+            (byte) 0xf4, (byte) 0x92, (byte) 0x7e, (byte) 0x9b, (byte) 0x2e, (byte) 0x2f,
+            (byte) 0x22, (byte) 0x3e, (byte) 0x5c, (byte) 0x67, (byte) 0xd7, (byte) 0x58,
+            (byte) 0xf6, (byte) 0xef, (byte) 0x1f, (byte) 0xb4, (byte) 0x04, (byte) 0xc7,
+            (byte) 0xfd, (byte) 0x8c, (byte) 0x4e, (byte) 0x27, (byte) 0x9e, (byte) 0xb9,
+            (byte) 0xef, (byte) 0x0f, (byte) 0xf7, (byte) 0x4a, (byte) 0xc2, (byte) 0xf4,
+            (byte) 0x64, (byte) 0x6b, (byte) 0xe0, (byte) 0xfb, (byte) 0xe3, (byte) 0x45,
+            (byte) 0xd5, (byte) 0x37, (byte) 0xa0, (byte) 0x2a, (byte) 0xc6, (byte) 0xf3,
+            (byte) 0xf6, (byte) 0xcc, (byte) 0xb5, (byte) 0x94, (byte) 0xbf, (byte) 0x56,
+            (byte) 0xa0, (byte) 0x61, (byte) 0x36, (byte) 0x88, (byte) 0x35, (byte) 0xd5,
+            (byte) 0xa5, (byte) 0xad, (byte) 0x20, (byte) 0x48, (byte) 0xda, (byte) 0x70,
+            (byte) 0x35, (byte) 0xd9, (byte) 0x75, (byte) 0x66, (byte) 0xa5, (byte) 0xac,
+            (byte) 0x86, (byte) 0x7a, (byte) 0x75, (byte) 0x49, (byte) 0x88, (byte) 0x40,
+            (byte) 0xce, (byte) 0xb0, (byte) 0x6f, (byte) 0x57, (byte) 0x15, (byte) 0x54,
+            (byte) 0xd3, (byte) 0x2f, (byte) 0x11, (byte) 0x9b, (byte) 0xe3, (byte) 0x87,
+            (byte) 0xc8, (byte) 0x8d, (byte) 0x98, (byte) 0xc6, (byte) 0xe0, (byte) 0xbc,
+            (byte) 0x85, (byte) 0xb9, (byte) 0x04, (byte) 0x43, (byte) 0xa9, (byte) 0x41,
+            (byte) 0xce, (byte) 0x42, (byte) 0x1a, (byte) 0x57, (byte) 0x10, (byte) 0xd8,
+            (byte) 0xe4, (byte) 0x6a, (byte) 0x51, (byte) 0x10, (byte) 0x0a, (byte) 0xec,
+            (byte) 0xe4, (byte) 0x57, (byte) 0xc7, (byte) 0xee, (byte) 0xe9, (byte) 0xd6,
+            (byte) 0xcb, (byte) 0x3e, (byte) 0xba, (byte) 0xfa, (byte) 0xe9, (byte) 0x0e,
+            (byte) 0xed, (byte) 0x87, (byte) 0x04, (byte) 0x9a, (byte) 0x48, (byte) 0xba,
+            (byte) 0xaf, (byte) 0x08, (byte) 0xf5, (byte) 0x02, (byte) 0x81, (byte) 0xc1,
+            (byte) 0x00, (byte) 0xcb, (byte) 0x63, (byte) 0xd6, (byte) 0x54, (byte) 0xb6,
+            (byte) 0xf3, (byte) 0xf3, (byte) 0x8c, (byte) 0xf8, (byte) 0xd0, (byte) 0xd2,
+            (byte) 0x84, (byte) 0xc1, (byte) 0xf5, (byte) 0x12, (byte) 0xe0, (byte) 0x02,
+            (byte) 0x80, (byte) 0x42, (byte) 0x92, (byte) 0x4e, (byte) 0xa4, (byte) 0x5c,
+            (byte) 0xa5, (byte) 0x64, (byte) 0xec, (byte) 0xb7, (byte) 0xdc, (byte) 0xe0,
+            (byte) 0x2d, (byte) 0x5d, (byte) 0xac, (byte) 0x0e, (byte) 0x24, (byte) 0x48,
+            (byte) 0x13, (byte) 0x05, (byte) 0xe8, (byte) 0xff, (byte) 0x96, (byte) 0x93,
+            (byte) 0xba, (byte) 0x3c, (byte) 0x88, (byte) 0xcc, (byte) 0x80, (byte) 0xf9,
+            (byte) 0xdb, (byte) 0xa8, (byte) 0x4d, (byte) 0x86, (byte) 0x47, (byte) 0xc8,
+            (byte) 0xbf, (byte) 0x34, (byte) 0x2d, (byte) 0xda, (byte) 0xb6, (byte) 0x28,
+            (byte) 0xf0, (byte) 0x1e, (byte) 0xd2, (byte) 0x46, (byte) 0x0d, (byte) 0x6f,
+            (byte) 0x36, (byte) 0x8e, (byte) 0x84, (byte) 0xd8, (byte) 0xaf, (byte) 0xf7,
+            (byte) 0x69, (byte) 0x23, (byte) 0x77, (byte) 0xfb, (byte) 0xc5, (byte) 0x04,
+            (byte) 0x08, (byte) 0x18, (byte) 0xac, (byte) 0x85, (byte) 0x80, (byte) 0x87,
+            (byte) 0x1c, (byte) 0xfe, (byte) 0x8e, (byte) 0x5d, (byte) 0x00, (byte) 0x7f,
+            (byte) 0x5b, (byte) 0x33, (byte) 0xf5, (byte) 0xdf, (byte) 0x70, (byte) 0x81,
+            (byte) 0xad, (byte) 0x81, (byte) 0xf4, (byte) 0x5a, (byte) 0x37, (byte) 0x8a,
+            (byte) 0x79, (byte) 0x09, (byte) 0xc5, (byte) 0x55, (byte) 0xab, (byte) 0x58,
+            (byte) 0x7c, (byte) 0x47, (byte) 0xca, (byte) 0xa5, (byte) 0x80, (byte) 0x49,
+            (byte) 0x5f, (byte) 0x71, (byte) 0x83, (byte) 0xfb, (byte) 0x3b, (byte) 0x06,
+            (byte) 0xec, (byte) 0x75, (byte) 0x23, (byte) 0xc4, (byte) 0x32, (byte) 0xc7,
+            (byte) 0x18, (byte) 0xf6, (byte) 0x82, (byte) 0x95, (byte) 0x98, (byte) 0x39,
+            (byte) 0xf7, (byte) 0x92, (byte) 0x31, (byte) 0xc0, (byte) 0x89, (byte) 0xba,
+            (byte) 0xd4, (byte) 0xd4, (byte) 0x58, (byte) 0x4e, (byte) 0x38, (byte) 0x35,
+            (byte) 0x10, (byte) 0xb9, (byte) 0xf1, (byte) 0x27, (byte) 0xdc, (byte) 0xff,
+            (byte) 0xc7, (byte) 0xb2, (byte) 0xba, (byte) 0x1f, (byte) 0x27, (byte) 0xaf,
+            (byte) 0x99, (byte) 0xd5, (byte) 0xb0, (byte) 0x39, (byte) 0xe7, (byte) 0x43,
+            (byte) 0x88, (byte) 0xd3, (byte) 0xce, (byte) 0x38, (byte) 0xc2, (byte) 0x99,
+            (byte) 0x43, (byte) 0xfc, (byte) 0x8a, (byte) 0xe3, (byte) 0x60, (byte) 0x0d,
+            (byte) 0x0a, (byte) 0xb8, (byte) 0xc4, (byte) 0x29, (byte) 0xca, (byte) 0x0d,
+            (byte) 0x30, (byte) 0xaf, (byte) 0xca, (byte) 0xd0, (byte) 0xaa, (byte) 0x67,
+            (byte) 0xb1, (byte) 0xdd, (byte) 0xdb, (byte) 0x7a, (byte) 0x11, (byte) 0xad,
+            (byte) 0xeb, (byte) 0x02, (byte) 0x81, (byte) 0xc0, (byte) 0x71, (byte) 0xb8,
+            (byte) 0xcf, (byte) 0x72, (byte) 0x35, (byte) 0x67, (byte) 0xb5, (byte) 0x38,
+            (byte) 0x8f, (byte) 0x16, (byte) 0xd3, (byte) 0x29, (byte) 0x82, (byte) 0x35,
+            (byte) 0x21, (byte) 0xd4, (byte) 0x49, (byte) 0x20, (byte) 0x74, (byte) 0x2d,
+            (byte) 0xc0, (byte) 0xa4, (byte) 0x44, (byte) 0xf5, (byte) 0xd8, (byte) 0xc9,
+            (byte) 0xe9, (byte) 0x90, (byte) 0x1d, (byte) 0xde, (byte) 0x3a, (byte) 0xa6,
+            (byte) 0xd7, (byte) 0xe5, (byte) 0xe8, (byte) 0x4e, (byte) 0x83, (byte) 0xd7,
+            (byte) 0xe6, (byte) 0x2f, (byte) 0x92, (byte) 0x31, (byte) 0x21, (byte) 0x3f,
+            (byte) 0xfa, (byte) 0xd2, (byte) 0x85, (byte) 0x92, (byte) 0x1f, (byte) 0xff,
+            (byte) 0x61, (byte) 0x00, (byte) 0xf6, (byte) 0xda, (byte) 0x6e, (byte) 0xc6,
+            (byte) 0x7f, (byte) 0x5a, (byte) 0x35, (byte) 0x79, (byte) 0xdc, (byte) 0xdc,
+            (byte) 0xa3, (byte) 0x2e, (byte) 0x9f, (byte) 0x35, (byte) 0xd1, (byte) 0x5c,
+            (byte) 0xda, (byte) 0xb9, (byte) 0xf7, (byte) 0x58, (byte) 0x7d, (byte) 0x4f,
+            (byte) 0xb6, (byte) 0x13, (byte) 0xd7, (byte) 0x2c, (byte) 0x0a, (byte) 0xa8,
+            (byte) 0x4d, (byte) 0xf2, (byte) 0xe4, (byte) 0x67, (byte) 0x4f, (byte) 0x8b,
+            (byte) 0xa6, (byte) 0xca, (byte) 0x1a, (byte) 0xbb, (byte) 0x02, (byte) 0x63,
+            (byte) 0x8f, (byte) 0xb7, (byte) 0x46, (byte) 0xec, (byte) 0x7a, (byte) 0x8a,
+            (byte) 0x09, (byte) 0x0a, (byte) 0x45, (byte) 0x3a, (byte) 0x8d, (byte) 0xa8,
+            (byte) 0x83, (byte) 0x4b, (byte) 0x0a, (byte) 0xdb, (byte) 0x4b, (byte) 0x99,
+            (byte) 0xf3, (byte) 0x69, (byte) 0x95, (byte) 0xf0, (byte) 0xcf, (byte) 0xe9,
+            (byte) 0xf7, (byte) 0x67, (byte) 0xc9, (byte) 0x45, (byte) 0x18, (byte) 0x2f,
+            (byte) 0xf0, (byte) 0x5c, (byte) 0x90, (byte) 0xbd, (byte) 0xa6, (byte) 0x66,
+            (byte) 0x8c, (byte) 0xfe, (byte) 0x60, (byte) 0x5d, (byte) 0x6c, (byte) 0x27,
+            (byte) 0xec, (byte) 0xc1, (byte) 0x84, (byte) 0xb2, (byte) 0xa1, (byte) 0x97,
+            (byte) 0x9e, (byte) 0x16, (byte) 0x29, (byte) 0xa7, (byte) 0xe0, (byte) 0x38,
+            (byte) 0xa2, (byte) 0x36, (byte) 0x05, (byte) 0x5f, (byte) 0xda, (byte) 0x72,
+            (byte) 0x1a, (byte) 0x5f, (byte) 0xa8, (byte) 0x7d, (byte) 0x41, (byte) 0x35,
+            (byte) 0xf6, (byte) 0x4e, (byte) 0x0a, (byte) 0x88, (byte) 0x8e, (byte) 0x00,
+            (byte) 0x98, (byte) 0xa6, (byte) 0xca, (byte) 0xc1, (byte) 0xdf, (byte) 0x72,
+            (byte) 0x6c, (byte) 0xfe, (byte) 0x29, (byte) 0xbe, (byte) 0xa3, (byte) 0x9b,
+            (byte) 0x0b, (byte) 0x5c, (byte) 0x0b, (byte) 0x9d, (byte) 0xa7, (byte) 0x71,
+            (byte) 0xce, (byte) 0x04, (byte) 0xfa, (byte) 0xac, (byte) 0x01, (byte) 0x8d,
+            (byte) 0x52, (byte) 0xa0, (byte) 0x3d, (byte) 0xdd, (byte) 0x02, (byte) 0x81,
+            (byte) 0xc1, (byte) 0x00, (byte) 0xc1, (byte) 0xc0, (byte) 0x2e, (byte) 0xa9,
+            (byte) 0xee, (byte) 0xca, (byte) 0xff, (byte) 0xe4, (byte) 0xf8, (byte) 0x15,
+            (byte) 0xfd, (byte) 0xa5, (byte) 0x68, (byte) 0x1b, (byte) 0x2d, (byte) 0x4a,
+            (byte) 0xe6, (byte) 0x37, (byte) 0x06, (byte) 0xb3, (byte) 0xd7, (byte) 0x64,
+            (byte) 0xad, (byte) 0xb9, (byte) 0x05, (byte) 0x26, (byte) 0x97, (byte) 0x94,
+            (byte) 0x3a, (byte) 0x9e, (byte) 0x1c, (byte) 0xd0, (byte) 0xcd, (byte) 0x7b,
+            (byte) 0xf4, (byte) 0x88, (byte) 0xe2, (byte) 0xa5, (byte) 0x6d, (byte) 0xed,
+            (byte) 0x24, (byte) 0x77, (byte) 0x52, (byte) 0x39, (byte) 0x43, (byte) 0x0f,
+            (byte) 0x4e, (byte) 0x75, (byte) 0xd8, (byte) 0xa3, (byte) 0x59, (byte) 0x5a,
+            (byte) 0xc2, (byte) 0xba, (byte) 0x9a, (byte) 0x5b, (byte) 0x60, (byte) 0x31,
+            (byte) 0x0d, (byte) 0x58, (byte) 0x89, (byte) 0x13, (byte) 0xe8, (byte) 0x95,
+            (byte) 0xdd, (byte) 0xae, (byte) 0xcc, (byte) 0x1f, (byte) 0x73, (byte) 0x48,
+            (byte) 0x55, (byte) 0xd8, (byte) 0xfb, (byte) 0x67, (byte) 0xce, (byte) 0x18,
+            (byte) 0x85, (byte) 0x59, (byte) 0xad, (byte) 0x1f, (byte) 0x93, (byte) 0xe1,
+            (byte) 0xb7, (byte) 0x54, (byte) 0x80, (byte) 0x8e, (byte) 0x5f, (byte) 0xbc,
+            (byte) 0x1c, (byte) 0x96, (byte) 0x66, (byte) 0x2e, (byte) 0x40, (byte) 0x17,
+            (byte) 0x2e, (byte) 0x01, (byte) 0x7a, (byte) 0x7d, (byte) 0xaa, (byte) 0xff,
+            (byte) 0xa3, (byte) 0xd2, (byte) 0xdf, (byte) 0xe2, (byte) 0xf3, (byte) 0x54,
+            (byte) 0x51, (byte) 0xeb, (byte) 0xba, (byte) 0x7c, (byte) 0x2a, (byte) 0x22,
+            (byte) 0xc6, (byte) 0x42, (byte) 0xbc, (byte) 0xa1, (byte) 0x6c, (byte) 0xcf,
+            (byte) 0x73, (byte) 0x2e, (byte) 0x07, (byte) 0xfc, (byte) 0xf5, (byte) 0x67,
+            (byte) 0x25, (byte) 0xd0, (byte) 0xfa, (byte) 0xeb, (byte) 0xb4, (byte) 0xd4,
+            (byte) 0x19, (byte) 0xcc, (byte) 0x64, (byte) 0xa1, (byte) 0x2e, (byte) 0x78,
+            (byte) 0x45, (byte) 0xd9, (byte) 0x7f, (byte) 0x1b, (byte) 0x4c, (byte) 0x10,
+            (byte) 0x31, (byte) 0x44, (byte) 0xe8, (byte) 0xcc, (byte) 0xf9, (byte) 0x1b,
+            (byte) 0x87, (byte) 0x31, (byte) 0xd6, (byte) 0x69, (byte) 0x85, (byte) 0x4a,
+            (byte) 0x49, (byte) 0xf6, (byte) 0xb2, (byte) 0xe0, (byte) 0xb8, (byte) 0x98,
+            (byte) 0x3c, (byte) 0xf6, (byte) 0x78, (byte) 0x46, (byte) 0xc8, (byte) 0x3d,
+            (byte) 0x60, (byte) 0xc1, (byte) 0xaa, (byte) 0x2f, (byte) 0x28, (byte) 0xa1,
+            (byte) 0x14, (byte) 0x6b, (byte) 0x75, (byte) 0x4d, (byte) 0xb1, (byte) 0x3d,
+            (byte) 0x80, (byte) 0x49, (byte) 0x33, (byte) 0xfd, (byte) 0x71, (byte) 0xc0,
+            (byte) 0x13, (byte) 0x1e, (byte) 0x16, (byte) 0x69, (byte) 0x80, (byte) 0xa4,
+            (byte) 0x9c, (byte) 0xd7, (byte) 0x02, (byte) 0x81, (byte) 0xc1, (byte) 0x00,
+            (byte) 0x8c, (byte) 0x33, (byte) 0x2d, (byte) 0xd9, (byte) 0xf3, (byte) 0x42,
+            (byte) 0x4d, (byte) 0xca, (byte) 0x5e, (byte) 0x60, (byte) 0x14, (byte) 0x10,
+            (byte) 0xf6, (byte) 0xf3, (byte) 0x71, (byte) 0x15, (byte) 0x88, (byte) 0x54,
+            (byte) 0x84, (byte) 0x21, (byte) 0x04, (byte) 0xb1, (byte) 0xaf, (byte) 0x02,
+            (byte) 0x11, (byte) 0x7f, (byte) 0x42, (byte) 0x3e, (byte) 0x86, (byte) 0xcb,
+            (byte) 0x6c, (byte) 0xf5, (byte) 0x57, (byte) 0x78, (byte) 0x4a, (byte) 0x03,
+            (byte) 0x9b, (byte) 0x80, (byte) 0xc2, (byte) 0x04, (byte) 0x3a, (byte) 0x6b,
+            (byte) 0xb3, (byte) 0x30, (byte) 0x31, (byte) 0x7e, (byte) 0xc3, (byte) 0x89,
+            (byte) 0x09, (byte) 0x4e, (byte) 0x86, (byte) 0x59, (byte) 0x41, (byte) 0xb5,
+            (byte) 0xae, (byte) 0xd5, (byte) 0xc6, (byte) 0x38, (byte) 0xbc, (byte) 0xd7,
+            (byte) 0xd7, (byte) 0x8e, (byte) 0xa3, (byte) 0x1a, (byte) 0xde, (byte) 0x32,
+            (byte) 0xad, (byte) 0x8d, (byte) 0x15, (byte) 0x81, (byte) 0xfe, (byte) 0xac,
+            (byte) 0xbd, (byte) 0xd0, (byte) 0xca, (byte) 0xbc, (byte) 0xd8, (byte) 0x6a,
+            (byte) 0xe1, (byte) 0xfe, (byte) 0xda, (byte) 0xc4, (byte) 0xd8, (byte) 0x62,
+            (byte) 0x71, (byte) 0x20, (byte) 0xa3, (byte) 0xd3, (byte) 0x06, (byte) 0x11,
+            (byte) 0xa9, (byte) 0x53, (byte) 0x7a, (byte) 0x44, (byte) 0x89, (byte) 0x3d,
+            (byte) 0x28, (byte) 0x5e, (byte) 0x7d, (byte) 0xf0, (byte) 0x60, (byte) 0xeb,
+            (byte) 0xb5, (byte) 0xdf, (byte) 0xed, (byte) 0x4f, (byte) 0x6d, (byte) 0x05,
+            (byte) 0x59, (byte) 0x06, (byte) 0xb0, (byte) 0x62, (byte) 0x50, (byte) 0x1c,
+            (byte) 0xb7, (byte) 0x2c, (byte) 0x44, (byte) 0xa4, (byte) 0x49, (byte) 0xf8,
+            (byte) 0x4f, (byte) 0x4b, (byte) 0xab, (byte) 0x71, (byte) 0x5b, (byte) 0xcb,
+            (byte) 0x31, (byte) 0x10, (byte) 0x41, (byte) 0xe0, (byte) 0x1a, (byte) 0x15,
+            (byte) 0xdc, (byte) 0x4c, (byte) 0x5d, (byte) 0x4f, (byte) 0x62, (byte) 0x83,
+            (byte) 0xa4, (byte) 0x80, (byte) 0x06, (byte) 0x36, (byte) 0xba, (byte) 0xc9,
+            (byte) 0xe2, (byte) 0xa4, (byte) 0x11, (byte) 0x98, (byte) 0x6b, (byte) 0x4c,
+            (byte) 0xe9, (byte) 0x90, (byte) 0x55, (byte) 0x18, (byte) 0xde, (byte) 0xe1,
+            (byte) 0x42, (byte) 0x38, (byte) 0x28, (byte) 0xa3, (byte) 0x54, (byte) 0x56,
+            (byte) 0x31, (byte) 0xaf, (byte) 0x5a, (byte) 0xd6, (byte) 0xf0, (byte) 0x26,
+            (byte) 0xe0, (byte) 0x7a, (byte) 0xd9, (byte) 0x6c, (byte) 0x64, (byte) 0xca,
+            (byte) 0x5d, (byte) 0x6d, (byte) 0x3d, (byte) 0x9a, (byte) 0xfe, (byte) 0x36,
+            (byte) 0x93, (byte) 0x9e, (byte) 0x62, (byte) 0x94, (byte) 0xc6, (byte) 0x07,
+            (byte) 0x83, (byte) 0x96, (byte) 0xd6, (byte) 0x27, (byte) 0xa6, (byte) 0xd8
+    };
+    public static final PrivateKey CLIENT_SUITE_B_RSA3072_KEY =
+            loadPrivateKey("RSA", CLIENT_SUITE_B_RSA3072_KEY_DATA);
+
+    private static final String CLIENT_SUITE_B_ECDSA_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIB9zCCAX4CFDpfSZh3AH07BEfGWuMDa7Ynz6y+MAoGCCqGSM49BAMDMF4xCzAJ\n"
+                    + "BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQKDAdB\n"
+                    + "bmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTESMBAGA1UEAwwJdW5pdGVzdENBMB4XDTIw\n"
+                    + "MDcyMTAyMjk1MFoXDTMwMDUzMDAyMjk1MFowYjELMAkGA1UEBhMCVVMxCzAJBgNV\n"
+                    + "BAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNVBAoMB0FuZHJvaWQxDjAMBgNVBAsM\n"
+                    + "BVdpLUZpMRYwFAYDVQQDDA11bml0ZXN0Q2xpZW50MHYwEAYHKoZIzj0CAQYFK4EE\n"
+                    + "ACIDYgAEhxhVJ7dcSqrto0X+dgRxtd8BWG8cWmPjBji3MIxDLfpcMDoIB84ae1Ew\n"
+                    + "gJn4YUYHrWsUDiVNihv8j7a/Ol1qcIY2ybH7tbezefLmagqA4vXEUXZXoUyL4ZNC\n"
+                    + "DWcdw6LrMAoGCCqGSM49BAMDA2cAMGQCMH4aP73HrriRUJRguiuRic+X4Cqj/7YQ\n"
+                    + "ueJmP87KF92/thhoQ9OrRo8uJITPmNDswwIwP2Q1AZCSL4BI9dYrqu07Ar+pSkXE\n"
+                    + "R7oOqGdZR+d/MvXcFSrbIaLKEoHXmQamIHLe\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_SUITE_B_ECDSA_CERT =
+            loadCertificate(CLIENT_SUITE_B_ECDSA_CERT_STRING);
+
+    private static final byte[] CLIENT_SUITE_B_ECC_KEY_DATA = new byte[]{
+            (byte) 0x30, (byte) 0x81, (byte) 0xb6, (byte) 0x02, (byte) 0x01, (byte) 0x00,
+            (byte) 0x30, (byte) 0x10, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
+            (byte) 0x05, (byte) 0x2b, (byte) 0x81, (byte) 0x04, (byte) 0x00, (byte) 0x22,
+            (byte) 0x04, (byte) 0x81, (byte) 0x9e, (byte) 0x30, (byte) 0x81, (byte) 0x9b,
+            (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x30, (byte) 0xea,
+            (byte) 0x6c, (byte) 0x4b, (byte) 0x6d, (byte) 0x43, (byte) 0xf9, (byte) 0x6c,
+            (byte) 0x91, (byte) 0xdc, (byte) 0x2d, (byte) 0x6e, (byte) 0x87, (byte) 0x4f,
+            (byte) 0x0a, (byte) 0x0b, (byte) 0x97, (byte) 0x25, (byte) 0x1c, (byte) 0x79,
+            (byte) 0xa2, (byte) 0x07, (byte) 0xdc, (byte) 0x94, (byte) 0xc2, (byte) 0xee,
+            (byte) 0x64, (byte) 0x51, (byte) 0x6d, (byte) 0x4e, (byte) 0x35, (byte) 0x1c,
+            (byte) 0x22, (byte) 0x2f, (byte) 0xc0, (byte) 0xea, (byte) 0x09, (byte) 0x47,
+            (byte) 0x3e, (byte) 0xb9, (byte) 0xb6, (byte) 0xb8, (byte) 0x83, (byte) 0x9e,
+            (byte) 0xed, (byte) 0x59, (byte) 0xe5, (byte) 0xe7, (byte) 0x0f, (byte) 0xa1,
+            (byte) 0x64, (byte) 0x03, (byte) 0x62, (byte) 0x00, (byte) 0x04, (byte) 0x87,
+            (byte) 0x18, (byte) 0x55, (byte) 0x27, (byte) 0xb7, (byte) 0x5c, (byte) 0x4a,
+            (byte) 0xaa, (byte) 0xed, (byte) 0xa3, (byte) 0x45, (byte) 0xfe, (byte) 0x76,
+            (byte) 0x04, (byte) 0x71, (byte) 0xb5, (byte) 0xdf, (byte) 0x01, (byte) 0x58,
+            (byte) 0x6f, (byte) 0x1c, (byte) 0x5a, (byte) 0x63, (byte) 0xe3, (byte) 0x06,
+            (byte) 0x38, (byte) 0xb7, (byte) 0x30, (byte) 0x8c, (byte) 0x43, (byte) 0x2d,
+            (byte) 0xfa, (byte) 0x5c, (byte) 0x30, (byte) 0x3a, (byte) 0x08, (byte) 0x07,
+            (byte) 0xce, (byte) 0x1a, (byte) 0x7b, (byte) 0x51, (byte) 0x30, (byte) 0x80,
+            (byte) 0x99, (byte) 0xf8, (byte) 0x61, (byte) 0x46, (byte) 0x07, (byte) 0xad,
+            (byte) 0x6b, (byte) 0x14, (byte) 0x0e, (byte) 0x25, (byte) 0x4d, (byte) 0x8a,
+            (byte) 0x1b, (byte) 0xfc, (byte) 0x8f, (byte) 0xb6, (byte) 0xbf, (byte) 0x3a,
+            (byte) 0x5d, (byte) 0x6a, (byte) 0x70, (byte) 0x86, (byte) 0x36, (byte) 0xc9,
+            (byte) 0xb1, (byte) 0xfb, (byte) 0xb5, (byte) 0xb7, (byte) 0xb3, (byte) 0x79,
+            (byte) 0xf2, (byte) 0xe6, (byte) 0x6a, (byte) 0x0a, (byte) 0x80, (byte) 0xe2,
+            (byte) 0xf5, (byte) 0xc4, (byte) 0x51, (byte) 0x76, (byte) 0x57, (byte) 0xa1,
+            (byte) 0x4c, (byte) 0x8b, (byte) 0xe1, (byte) 0x93, (byte) 0x42, (byte) 0x0d,
+            (byte) 0x67, (byte) 0x1d, (byte) 0xc3, (byte) 0xa2, (byte) 0xeb
+    };
+    public static final PrivateKey CLIENT_SUITE_B_ECC_KEY =
+            loadPrivateKey("EC", CLIENT_SUITE_B_ECC_KEY_DATA);
+
+    private static X509Certificate loadCertificate(String blob) {
+        try {
+            final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+            InputStream stream = new ByteArrayInputStream(blob.getBytes(StandardCharsets.UTF_8));
+
+            return (X509Certificate) certFactory.generateCertificate(stream);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private static PrivateKey loadPrivateKey(String algorithm, byte[] fakeKey) {
+        try {
+            KeyFactory kf = KeyFactory.getInstance(algorithm);
+            return kf.generatePrivate(new PKCS8EncodedKeySpec(fakeKey));
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            return null;
+        }
+    }
+
     private WifiNetworkSuggestion.Builder createBuilderWithCommonParams() {
         return createBuilderWithCommonParams(false);
     }
@@ -69,7 +540,7 @@
             builder.setIsEnhancedOpen(false);
             builder.setIsHiddenSsid(true);
         }
-        builder.setPriority(0);
+        builder.setPriority(TEST_PRIORITY);
         builder.setIsAppInteractionRequired(true);
         builder.setIsUserInteractionRequired(true);
         builder.setIsMetered(true);
@@ -77,6 +548,11 @@
         builder.setCredentialSharedWithUser(true);
         builder.setIsInitialAutojoinEnabled(true);
         builder.setUntrusted(false);
+        if (BuildCompat.isAtLeastS()) {
+            builder.setOemPaid(false);
+            builder.setSubscriptionId(TEST_SUB_ID);
+            builder.setPriorityGroup(TEST_PRIORITY_GROUP);
+        }
         return builder;
     }
 
@@ -93,13 +569,18 @@
             assertFalse(suggestion.isEnhancedOpen());
             assertTrue(suggestion.isHiddenSsid());
         }
-        assertEquals(0, suggestion.getPriority());
+        assertEquals(TEST_PRIORITY, suggestion.getPriority());
         assertTrue(suggestion.isAppInteractionRequired());
         assertTrue(suggestion.isUserInteractionRequired());
         assertTrue(suggestion.isMetered());
         assertTrue(suggestion.isCredentialSharedWithUser());
         assertTrue(suggestion.isInitialAutojoinEnabled());
         assertFalse(suggestion.isUntrusted());
+        if (BuildCompat.isAtLeastS()) {
+            assertFalse(suggestion.isOemPaid());
+            assertEquals(TEST_PRIORITY_GROUP, suggestion.getPriorityGroup());
+            assertEquals(TEST_SUB_ID, suggestion.getSubscriptionId());
+        }
     }
 
     /**
@@ -116,6 +597,7 @@
                 .build();
         validateCommonParams(suggestion);
         assertEquals(TEST_PASSPHRASE, suggestion.getPassphrase());
+        assertNull(suggestion.getEnterpriseConfig());
         assertNull(suggestion.getPasspointConfig());
     }
 
@@ -133,6 +615,7 @@
                         .build();
         validateCommonParams(suggestion);
         assertEquals(TEST_PASSPHRASE, suggestion.getPassphrase());
+        assertNull(suggestion.getEnterpriseConfig());
         assertNull(suggestion.getPasspointConfig());
     }
 
@@ -150,6 +633,7 @@
                         .build();
         validateCommonParams(suggestion);
         assertEquals(TEST_PASSPHRASE, suggestion.getPassphrase());
+        assertNull(suggestion.getEnterpriseConfig());
         assertNull(suggestion.getPasspointConfig());
     }
 
@@ -204,6 +688,58 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    public void testBuilderWithWpa3EnterpriseSuiteBRsa() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(CA_SUITE_B_RSA3072_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(CLIENT_SUITE_B_RSA3072_KEY,
+                new X509Certificate[] {CLIENT_SUITE_B_RSA3072_CERT});
+        enterpriseConfig.setAltSubjectMatch("domain.com");
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWpa3EnterpriseConfig(enterpriseConfig)
+                        .build();
+        validateCommonParams(suggestion);
+        assertNull(suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertEquals(enterpriseConfig.getEapMethod(),
+                suggestion.getEnterpriseConfig().getEapMethod());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithWpa3EnterpriseSuiteBEcc() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {CLIENT_SUITE_B_ECDSA_CERT});
+        enterpriseConfig.setAltSubjectMatch("domain.com");
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWpa3EnterpriseConfig(enterpriseConfig)
+                        .build();
+        validateCommonParams(suggestion);
+        assertNull(suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertEquals(enterpriseConfig.getEapMethod(),
+                suggestion.getEnterpriseConfig().getEapMethod());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
     public void testBuilderWithWapiEnterprise() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -264,6 +800,7 @@
                         .build();
         validateCommonParams(suggestion, true);
         assertNull(suggestion.getPassphrase());
+        assertNull(suggestion.getEnterpriseConfig());
         assertEquals(passpointConfig, suggestion.getPasspointConfig());
     }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/passpoint/cts/HomeSpTest.java b/tests/tests/wifi/src/android/net/wifi/passpoint/cts/HomeSpTest.java
new file mode 100644
index 0000000..b67bdf2
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/passpoint/cts/HomeSpTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.passpoint.cts;
+
+import android.net.wifi.cts.WifiJUnit3TestBase;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SmallTest;
+
+import java.util.Arrays;
+
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@SmallTest
+public class HomeSpTest extends WifiJUnit3TestBase {
+    /**
+     * Verify that the anyOis set and get APIs work as expected
+     */
+    public void testAnyOis() throws Exception {
+        HomeSp homeSp = new HomeSp();
+        assertNull(homeSp.getMatchAnyOis());
+        final long[] ois = new long[]{0x1000, 0x2000};
+        homeSp.setMatchAnyOis(ois);
+        final long[] profileOis = homeSp.getMatchAnyOis();
+        assertTrue(Arrays.equals(ois, profileOis));
+    }
+
+    /**
+     * Verify that the allOis set and get APIs work as expected
+     */
+    public void testAllOis() throws Exception {
+        HomeSp homeSp = new HomeSp();
+        assertNull(homeSp.getMatchAllOis());
+        final long[] ois = new long[]{0x1000, 0x2000};
+        homeSp.setMatchAllOis(ois);
+        final long[] profileOis = homeSp.getMatchAllOis();
+        assertTrue(Arrays.equals(ois, profileOis));
+    }
+
+    /**
+     * Verify that the OtherHomePartners set and get APIs work as expected
+     */
+    public void testOtherHomePartners() throws Exception {
+        HomeSp homeSp = new HomeSp();
+        assertNull(homeSp.getOtherHomePartners());
+        final String[] homePartners = new String[] {"other-provider1", "other-provider2"};
+        homeSp.setOtherHomePartners(homePartners);
+        final String[] profileHomePartners = homeSp.getOtherHomePartners();
+        assertTrue(Arrays.equals(homePartners, profileHomePartners));
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java b/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java
new file mode 100644
index 0000000..00556a1
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.passpoint.cts;
+
+import static org.junit.Assert.assertNotEquals;
+
+import android.net.wifi.cts.FakeKeys;
+import android.net.wifi.cts.WifiJUnit3TestBase;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SmallTest;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@SmallTest
+public class PasspointConfigurationTest extends WifiJUnit3TestBase {
+    private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
+    public static final int EAP_SIM = 18;
+    public static final int EAP_TTLS = 21;
+
+    /**
+     * Verify that the unique identifier generated is identical for two instances
+     */
+    public void testEqualUniqueId() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+        PasspointConfiguration config2 = createConfig();
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is the same for two instances with different
+     * HomeSp node but same FQDN
+     */
+    public void testUniqueIdSameHomeSpSameFqdn() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+        HomeSp homeSp = config1.getHomeSp();
+        homeSp.setMatchAnyOis(new long[]{0x1000, 0x2000});
+
+        // Modify config2's RCOIs and friendly name to a different set of values
+        PasspointConfiguration config2 = createConfig();
+        homeSp = config2.getHomeSp();
+
+        homeSp.setRoamingConsortiumOis(new long[]{0xaa, 0xbb});
+        homeSp.setFriendlyName("Some other name");
+        homeSp.setOtherHomePartners(new String[]{"other-provider1", "other-provider2"});
+        homeSp.setMatchAllOis(new long[]{0x1000, 0x2000});
+        config2.setHomeSp(homeSp);
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with the same
+     * HomeSp node but different FQDN
+     */
+    public void testUniqueIdDifferentHomeSpDifferentFqdn() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+
+        // Modify config2's FQDN to a different value
+        PasspointConfiguration config2 = createConfig();
+        HomeSp homeSp = config2.getHomeSp();
+        homeSp.setFqdn("fqdn2.com");
+        config2.setHomeSp(homeSp);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * SIM Credential node
+     */
+    public void testUniqueIdDifferentSimCredential() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+
+        // Modify config2's realm and SIM credential to a different set of values
+        PasspointConfiguration config2 = createConfig();
+        Credential credential = config2.getCredential();
+        credential.setRealm("realm2.example.com");
+        credential.getSimCredential().setImsi("350460*");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * Realm in the Credential node
+     */
+    public void testUniqueIdDifferentRealm() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+
+        // Modify config2's realm to a different set of values
+        PasspointConfiguration config2 = createConfig();
+        Credential credential = config2.getCredential();
+        credential.setRealm("realm2.example.com");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is the same for two instances with different
+     * password and same username in the User Credential node
+     */
+    public void testUniqueIdSameUserInUserCredential() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+        Credential credential = createCredentialWithUserCredential("user", "passwd");
+        config1.setCredential(credential);
+
+        // Modify config2's Passpowrd to a different set of values
+        PasspointConfiguration config2 = createConfig();
+        credential = createCredentialWithUserCredential("user", "newpasswd");
+        config2.setCredential(credential);
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * username in the User Credential node
+     */
+    public void testUniqueIdDifferentUserCredential() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+        Credential credential = createCredentialWithUserCredential("user", "passwd");
+        config1.setCredential(credential);
+
+        // Modify config2's username to a different value
+        PasspointConfiguration config2 = createConfig();
+        credential = createCredentialWithUserCredential("user2", "passwd");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * Cert Credential node
+     */
+    public void testUniqueIdDifferentCertCredential() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+        Credential credential = createCredentialWithCertificateCredential(true, true);
+        config1.setCredential(credential);
+
+        // Modify config2's cert credential to a different set of values
+        PasspointConfiguration config2 = createConfig();
+        credential = createCredentialWithCertificateCredential(false, false);
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Helper function for generating user credential for testing.
+     *
+     * @return {@link Credential}
+     */
+    private static Credential createCredentialWithUserCredential(String username, String password) {
+        Credential.UserCredential userCred = new Credential.UserCredential();
+        userCred.setUsername(username);
+        userCred.setPassword(password);
+        userCred.setEapType(EAP_TTLS);
+        userCred.setNonEapInnerMethod("MS-CHAP");
+        return createCredential(userCred, null, null, null, null, FakeKeys.CA_CERT0);
+    }
+
+    /**
+     * Helper function for generating Credential for testing.
+     *
+     * @param userCred               Instance of UserCredential
+     * @param certCred               Instance of CertificateCredential
+     * @param simCred                Instance of SimCredential
+     * @param clientCertificateChain Chain of client certificates
+     * @param clientPrivateKey       Client private key
+     * @param caCerts                CA certificates
+     * @return {@link Credential}
+     */
+    private static Credential createCredential(Credential.UserCredential userCred,
+            Credential.CertificateCredential certCred,
+            Credential.SimCredential simCred,
+            X509Certificate[] clientCertificateChain, PrivateKey clientPrivateKey,
+            X509Certificate... caCerts) {
+        Credential cred = new Credential();
+        cred.setRealm("realm");
+        cred.setUserCredential(userCred);
+        cred.setCertCredential(certCred);
+        cred.setSimCredential(simCred);
+        cred.setCaCertificate(caCerts[0]);
+        cred.setClientCertificateChain(clientCertificateChain);
+        cred.setClientPrivateKey(clientPrivateKey);
+        return cred;
+    }
+
+    /**
+     * Helper function for generating certificate credential for testing.
+     *
+     * @return {@link Credential}
+     */
+    private static Credential createCredentialWithCertificateCredential(Boolean useCaCert0,
+            Boolean useCert0)
+            throws NoSuchAlgorithmException, CertificateEncodingException {
+        Credential.CertificateCredential certCred = new Credential.CertificateCredential();
+        certCred.setCertType("x509v3");
+        if (useCert0) {
+            certCred.setCertSha256Fingerprint(
+                    MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
+        } else {
+            certCred.setCertSha256Fingerprint(MessageDigest.getInstance("SHA-256")
+                    .digest(FakeKeys.CLIENT_SUITE_B_RSA3072_CERT.getEncoded()));
+        }
+        return createCredential(null, certCred, null, new X509Certificate[]{FakeKeys.CLIENT_CERT},
+                FakeKeys.RSA_KEY1, useCaCert0 ? FakeKeys.CA_CERT0 : FakeKeys.CA_CERT1);
+    }
+
+    /**
+     * Helper function for creating a {@link PasspointConfiguration} for testing.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    private static PasspointConfiguration createConfig() {
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.setHomeSp(createHomeSp());
+        config.setCredential(createCredential());
+        Map<String, byte[]> trustRootCertList = new HashMap<>();
+        trustRootCertList.put("trustRoot.cert1.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        trustRootCertList.put("trustRoot.cert2.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        return config;
+    }
+
+    /**
+     * Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSp} for testing.
+     *
+     * @return {@link android.net.wifi.hotspot2.pps.HomeSp}
+     */
+    private static HomeSp createHomeSp() {
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("fqdn");
+        homeSp.setFriendlyName("friendly name");
+        homeSp.setRoamingConsortiumOis(new long[]{0x55, 0x66});
+        return homeSp;
+    }
+
+    /**
+     * Utility function for creating a {@link android.net.wifi.hotspot2.pps.Credential} for
+     * testing..
+     *
+     * @return {@link android.net.wifi.hotspot2.pps.Credential}
+     */
+    private static Credential createCredential() {
+        Credential cred = new Credential();
+        cred.setRealm("realm");
+        cred.setUserCredential(null);
+        cred.setCertCredential(null);
+        cred.setSimCredential(new Credential.SimCredential());
+        cred.getSimCredential().setImsi("1234*");
+        cred.getSimCredential().setEapType(EAP_SIM);
+        cred.setCaCertificate(null);
+        cred.setClientCertificateChain(null);
+        cred.setClientPrivateKey(null);
+        return cred;
+    }
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
index 9c0078d..88113e9 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
@@ -31,7 +31,6 @@
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
-import android.test.AndroidTestCase;
 
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -56,6 +55,9 @@
     // wait for network selection and connection finish
     private static final int WAIT_FOR_CONNECTION_FINISH_MS = 30_000;
 
+    // Interval between failure scans
+    private static final int INTERVAL_BETWEEN_FAILURE_SCAN_MILLIS = 5_000;
+
     protected WifiRttManager mWifiRttManager;
     protected WifiManager mWifiManager;
     private LocationManager mLocationManager;
@@ -234,10 +236,12 @@
                     bestTestAp = scanResult;
                 }
             }
-
+            if (bestTestAp == null) {
+                // Ongoing connection may cause scan failure, wait for a while before next scan.
+                Thread.sleep(INTERVAL_BETWEEN_FAILURE_SCAN_MILLIS);
+            }
             scanCount++;
         }
-
         return bestTestAp;
     }
 }
diff --git a/tests/tests/wrap/nowrap/AndroidManifest.xml b/tests/tests/wrap/nowrap/AndroidManifest.xml
index 927f1ef..c42ec94 100644
--- a/tests/tests/wrap/nowrap/AndroidManifest.xml
+++ b/tests/tests/wrap/nowrap/AndroidManifest.xml
@@ -16,25 +16,26 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.wrap.nowrap.cts">
+     package="android.wrap.nowrap.cts">
 
     <!-- Ensure that wrap.sh is extracted. -->
-    <application android:debuggable="true" android:extractNativeLibs="true">
-        <uses-library android:name="android.test.runner" />
-        <meta-data android:name="android.wrap.cts.expext_env" android:value="false" />
-        <activity android:name="android.wrap.WrapActivity" >
+    <application android:debuggable="true"
+         android:extractNativeLibs="true">
+        <uses-library android:name="android.test.runner"/>
+        <meta-data android:name="android.wrap.cts.expext_env"
+             android:value="false"/>
+        <activity android:name="android.wrap.WrapActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for wrap.sh"
-        android:targetPackage="android.wrap.nowrap.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for wrap.sh"
+         android:targetPackage="android.wrap.nowrap.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/wrap/wrap_debug/AndroidManifest.xml b/tests/tests/wrap/wrap_debug/AndroidManifest.xml
index b68aefb..aed6388 100644
--- a/tests/tests/wrap/wrap_debug/AndroidManifest.xml
+++ b/tests/tests/wrap/wrap_debug/AndroidManifest.xml
@@ -16,25 +16,26 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.wrap.wrap_debug.cts">
+     package="android.wrap.wrap_debug.cts">
 
     <!-- Ensure that wrap.sh is extracted. -->
-    <application android:debuggable="true" android:extractNativeLibs="true">
-        <uses-library android:name="android.test.runner" />
-        <meta-data android:name="android.wrap.cts.expext_env" android:value="true" />
-        <activity android:name="android.wrap.WrapActivity" >
+    <application android:debuggable="true"
+         android:extractNativeLibs="true">
+        <uses-library android:name="android.test.runner"/>
+        <meta-data android:name="android.wrap.cts.expext_env"
+             android:value="true"/>
+        <activity android:name="android.wrap.WrapActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for wrap.sh"
-        android:targetPackage="android.wrap.wrap_debug.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for wrap.sh"
+         android:targetPackage="android.wrap.wrap_debug.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidManifest.xml b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidManifest.xml
index d00194b..d52119a 100644
--- a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidManifest.xml
+++ b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidManifest.xml
@@ -16,25 +16,26 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.wrap.wrap_debug_malloc_debug.cts">
+     package="android.wrap.wrap_debug_malloc_debug.cts">
 
     <!-- Ensure that wrap.sh is extracted. -->
-    <application android:debuggable="true" android:extractNativeLibs="true">
-        <uses-library android:name="android.test.runner" />
-        <meta-data android:name="android.wrap.cts.expext_env" android:value="true" />
-        <activity android:name="android.wrap.WrapActivity" >
+    <application android:debuggable="true"
+         android:extractNativeLibs="true">
+        <uses-library android:name="android.test.runner"/>
+        <meta-data android:name="android.wrap.cts.expext_env"
+             android:value="true"/>
+        <activity android:name="android.wrap.WrapActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for wrap.sh"
-        android:targetPackage="android.wrap.wrap_debug_malloc_debug.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for wrap.sh"
+         android:targetPackage="android.wrap.wrap_debug_malloc_debug.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/wrap/wrap_nodebug/AndroidManifest.xml b/tests/tests/wrap/wrap_nodebug/AndroidManifest.xml
index 9504883..b638726 100644
--- a/tests/tests/wrap/wrap_nodebug/AndroidManifest.xml
+++ b/tests/tests/wrap/wrap_nodebug/AndroidManifest.xml
@@ -16,25 +16,25 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.wrap.wrap_nodebug.cts">
+     package="android.wrap.wrap_nodebug.cts">
 
     <!-- Ensure that wrap.sh is extracted. -->
     <application android:extractNativeLibs="true">
-        <uses-library android:name="android.test.runner" />
-        <meta-data android:name="android.wrap.cts.expext_env" android:value="false" />
-        <activity android:name="android.wrap.WrapActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <meta-data android:name="android.wrap.cts.expext_env"
+             android:value="false"/>
+        <activity android:name="android.wrap.WrapActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for wrap.sh"
-        android:targetPackage="android.wrap.wrap_nodebug.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for wrap.sh"
+         android:targetPackage="android.wrap.wrap_nodebug.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/tvprovider/TEST_MAPPING b/tests/tvprovider/TEST_MAPPING
new file mode 100644
index 0000000..a0b06c4
--- /dev/null
+++ b/tests/tvprovider/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTvProviderTestCases"
+    }
+  ]
+}
diff --git a/tests/vr/AndroidManifest.xml b/tests/vr/AndroidManifest.xml
index cc60bd2..0196566 100644
--- a/tests/vr/AndroidManifest.xml
+++ b/tests/vr/AndroidManifest.xml
@@ -13,49 +13,48 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.vr.cts"
-    android:versionCode="1"
-    android:versionName="1.0" >
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-sdk android:minSdkVersion="14" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.vr.cts"
+     android:versionCode="1"
+     android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-sdk android:minSdkVersion="14"/>
     <uses-feature android:glEsVersion="0x00020000"/>
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.vr.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.vr.cts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
-    <application
-        android:icon="@drawable/ic_launcher"
-        android:label="@string/app_name"
-        android:hardwareAccelerated="false" >
+    <application android:icon="@drawable/ic_launcher"
+         android:label="@string/app_name"
+         android:hardwareAccelerated="false">
 
 	<service android:name="com.android.cts.verifier.vr.MockVrListenerService"
-            android:exported="true"
-            android:enabled="true"
-            android:label="@string/vr_service_name"
-            android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
+    	 android:exported="true"
+    	 android:enabled="true"
+    	 android:label="@string/vr_service_name"
+    	 android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
             <intent-filter>
-                <action android:name="android.service.vr.VrListenerService" />
+                <action android:name="android.service.vr.VrListenerService"/>
             </intent-filter>
         </service>
 
-         <activity
-            android:label="@string/app_name"
-            android:name="android.vr.cts.OpenGLESActivity">
+         <activity android:label="@string/app_name"
+              android:name="android.vr.cts.OpenGLESActivity">
          </activity>
          <activity android:name=".CtsActivity"
-                  android:label="CtsActivity">
+              android:label="CtsActivity"
+              android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
-         <uses-library  android:name="android.test.runner" />
+         <uses-library android:name="android.test.runner"/>
     </application>
 
 </manifest>
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
index e0fcbb0..8716562 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
@@ -536,6 +536,7 @@
         charsKeyNames.add(CameraCharacteristics.SCALER_CROPPING_TYPE.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS.getName());
+        charsKeyNames.add(CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1.getName());
diff --git a/tools/cts-holo-generation/AndroidManifest.xml b/tools/cts-holo-generation/AndroidManifest.xml
index 41fab00..85332bb 100644
--- a/tools/cts-holo-generation/AndroidManifest.xml
+++ b/tools/cts-holo-generation/AndroidManifest.xml
@@ -1,20 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="com.android.cts.holo_capture"
-      android:versionCode="1"
-      android:versionName="1.0">
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.holo_capture" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.holo_capture"
+     android:versionCode="1"
+     android:versionName="1.0">
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.holo_capture"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name=".CaptureActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".CaptureActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
diff --git a/tools/device-setup/TestDeviceSetup/AndroidManifest.xml b/tools/device-setup/TestDeviceSetup/AndroidManifest.xml
index 0a20e1c..5771fa2 100644
--- a/tools/device-setup/TestDeviceSetup/AndroidManifest.xml
+++ b/tools/device-setup/TestDeviceSetup/AndroidManifest.xml
@@ -16,15 +16,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.tests.devicesetup">
+     package="android.tests.devicesetup">
 
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.tests.getinfo.DeviceInfoActivity"
-                  android:label="DeviceInfoActivity">
+             android:label="DeviceInfoActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -33,7 +34,7 @@
     </application>
 
     <instrumentation android:name="android.tests.getinfo.DeviceInfoInstrument"
-                     android:targetPackage="android.tests.devicesetup"
-                     android:label="app to get info from device"/>
+         android:targetPackage="android.tests.devicesetup"
+         android:label="app to get info from device"/>
 
 </manifest>
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/XmlHandler.java b/tools/release-parser/src/com/android/cts/releaseparser/XmlHandler.java
index c30e524..0128dcd 100644
--- a/tools/release-parser/src/com/android/cts/releaseparser/XmlHandler.java
+++ b/tools/release-parser/src/com/android/cts/releaseparser/XmlHandler.java
@@ -33,7 +33,6 @@
     public static final String ASSIGN_PERMISSION_TAG = "assign-permission";
     public static final String LIBRARY_TAG = "library";
     public static final String ALLOW_IN_POWER_SAVE_TAG = "allow-in-power-save";
-    public static final String SYSTEM_USER_WHITELISTED_TAG = "system-user-whitelisted-app";
     public static final String PRIVAPP_PERMISSIONS_TAG = "privapp-permissions";
     public static final String FEATURE_TAG = "feature";
 
@@ -52,7 +51,6 @@
     private PermissionList.Builder mAssignPermissionsListBuilder;
     private PermissionList.Builder mLibraryListBuilder;
     private PermissionList.Builder mAllowInPowerSaveListBuilder;
-    private PermissionList.Builder mSystemUserWhitelistedListBuilder;
     private PermissionList.Builder mPrivappPermissionsListBuilder;
     private PermissionList.Builder mFeatureListBuilder;
 
@@ -66,8 +64,6 @@
         mLibraryListBuilder.setName(LIBRARY_TAG);
         mAllowInPowerSaveListBuilder = PermissionList.newBuilder();
         mAllowInPowerSaveListBuilder.setName(ALLOW_IN_POWER_SAVE_TAG);
-        mSystemUserWhitelistedListBuilder = PermissionList.newBuilder();
-        mSystemUserWhitelistedListBuilder.setName(SYSTEM_USER_WHITELISTED_TAG);
         mPrivappPermissionsListBuilder = PermissionList.newBuilder();
         mPrivappPermissionsListBuilder.setName(PRIVAPP_PERMISSIONS_TAG);
         mFeatureListBuilder = PermissionList.newBuilder();
@@ -125,10 +121,6 @@
                 mPermissionsBuilder = Permission.newBuilder();
                 mPermissionsBuilder.setName(attributes.getValue(PACKAGE_TAG));
                 break;
-            case SYSTEM_USER_WHITELISTED_TAG:
-                mPermissionsBuilder = Permission.newBuilder();
-                mPermissionsBuilder.setName(attributes.getValue(PACKAGE_TAG));
-                break;
             case PRIVAPP_PERMISSIONS_TAG:
                 mPermissionsBuilder = Permission.newBuilder();
                 mPermissionsBuilder.setName(attributes.getValue(PACKAGE_TAG));
@@ -164,10 +156,6 @@
                 if (mAllowInPowerSaveListBuilder.getPermissionsList().size() > 0) {
                     mPermissions.put(ALLOW_IN_POWER_SAVE_TAG, mAllowInPowerSaveListBuilder.build());
                 }
-                if (mSystemUserWhitelistedListBuilder.getPermissionsList().size() > 0) {
-                    mPermissions.put(
-                            SYSTEM_USER_WHITELISTED_TAG, mSystemUserWhitelistedListBuilder.build());
-                }
                 if (mPrivappPermissionsListBuilder.getPermissionsList().size() > 0) {
                     mPermissions.put(
                             PRIVAPP_PERMISSIONS_TAG, mPrivappPermissionsListBuilder.build());
@@ -192,10 +180,6 @@
                 mAllowInPowerSaveListBuilder.addPermissions(mPermissionsBuilder.build());
                 mPermissionsBuilder = null;
                 break;
-            case SYSTEM_USER_WHITELISTED_TAG:
-                mSystemUserWhitelistedListBuilder.addPermissions(mPermissionsBuilder.build());
-                mPermissionsBuilder = null;
-                break;
             case PRIVAPP_PERMISSIONS_TAG:
                 mPrivappPermissionsListBuilder.addPermissions(mPermissionsBuilder.build());
                 mPermissionsBuilder = null;
diff --git a/tools/release-parser/tests/resources/platform.xml b/tools/release-parser/tests/resources/platform.xml
index ab90e1b..5895f77 100644
--- a/tools/release-parser/tests/resources/platform.xml
+++ b/tools/release-parser/tests/resources/platform.xml
@@ -207,10 +207,4 @@
     <!-- Whitelist system providers -->
     <allow-in-power-save-except-idle package="com.android.providers.calendar" />
     <allow-in-power-save-except-idle package="com.android.providers.contacts" />
-
-    <!-- These are the packages that are white-listed to be able to run as system user -->
-    <system-user-whitelisted-app package="com.android.settings" />
-
-    <!-- These are the packages that shouldn't run as system user -->
-    <system-user-blacklisted-app package="com.android.wallpaper.livepicker" />
 </permissions>
diff --git a/tools/release-parser/tests/resources/platform.xml.pb.txt b/tools/release-parser/tests/resources/platform.xml.pb.txt
index 14b589e..d5fff53 100644
--- a/tools/release-parser/tests/resources/platform.xml.pb.txt
+++ b/tools/release-parser/tests/resources/platform.xml.pb.txt
@@ -59,15 +59,6 @@
   }
 }
 device_permissions {
-  key: "system-user-whitelisted-app"
-  value {
-    name: "system-user-whitelisted-app"
-    permissions {
-      name: "com.android.settings"
-    }
-  }
-}
-device_permissions {
   key: "permission"
   value {
     name: "permission"